conjur-debify 3.0.0.pre.1118 → 3.0.1.pre.2

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 (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 +255 -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 +10 -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 +23 -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 +851 -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 +109 -0
  49. data/lib/conjur/publish/Dockerfile +5 -0
  50. data/publish-rubygem.sh +12 -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 +77 -4
@@ -0,0 +1,851 @@
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
+ 'name' => project_name,
544
+ 'Env' => [
545
+ "CONJUR_AUTHN_LOGIN=admin",
546
+ "CONJUR_ENV=appliance",
547
+ "CONJUR_AUTHN_API_KEY=SEcret12!!!!",
548
+ "CONJUR_ADMIN_PASSWORD=SEcret12!!!!",
549
+ ] + global_options[:env],
550
+ 'HostConfig' => {
551
+ 'Binds' => [
552
+ [dir, "/src/#{project_name}"].join(':')
553
+ ]
554
+ }
555
+ }
556
+ host_config = options['HostConfig']
557
+
558
+ host_config['Privileged'] = true if Docker.version['Version'] >= '1.10.0'
559
+ host_config['VolumesFrom'] = cmd_options[:'volumes-from'] if cmd_options[:'volumes-from'] && !cmd_options[:'volumes-from'].empty?
560
+
561
+ add_network_config(options, cmd_options)
562
+
563
+ if global_options[:'local-bundle']
564
+ host_config['Binds']
565
+ .push([vendor_dir, "/src/#{project_name}/vendor"].join(':'))
566
+ .push([dot_bundle_dir, "/src/#{project_name}/.bundle"].join(':'))
567
+ end
568
+
569
+ container = Docker::Container.create(options.tap { |o| DebugMixin.debug_write "creating container with options #{o.inspect}" })
570
+
571
+ begin
572
+ DebugMixin.debug_write "Testing #{project_name} in container #{container.id}\n"
573
+
574
+ spawn("docker logs -f #{container.id}", [:out, :err] => $stderr).tap do |pid|
575
+ Process.detach pid
576
+ end
577
+ container.start!
578
+
579
+ # Wait for pg/main so that migrations can run
580
+ 30.times do
581
+ stdout, stderr, exitcode = container.exec %w(sv status pg/main), &DebugMixin::DOCKER
582
+ status = stdout.join
583
+ break if exitcode == 0 && status =~ /^run\:/
584
+ sleep 1
585
+ end
586
+
587
+ # If we're not using shared gems, run dev-install instead of
588
+ # test-install. Even having to reinstall all the gems is
589
+ # better than dealing with Docker For Mac's current file
590
+ # sharing performance.
591
+ install_cmd = global_options[:'local-bundle'] ? 'test-install' : 'dev-install'
592
+ container_command container, "/opt/conjur/evoke/bin/#{install_cmd}", project_name
593
+
594
+ DebugMixin.debug_write "Starting conjur\n"
595
+
596
+ container_command container, "rm", "/etc/service/conjur/down"
597
+ container_command container, "sv", "start", "conjur"
598
+ wait_for_conjur appliance_image, container
599
+
600
+ system "./#{test_script} #{container.id}"
601
+ exit_now! "#{test_script} failed with exit code #{$?.exitstatus}", $?.exitstatus unless $?.exitstatus == 0
602
+ ensure
603
+ unless cmd_options[:keep] || ENV['KEEP_CONTAINERS']
604
+ DebugMixin.debug_write "deleting container"
605
+ container.delete(force: true)
606
+ end
607
+ end
608
+ end
609
+ end
610
+ end
611
+
612
+ desc "Setup a development sandbox for a Conjur debian package in a Conjur appliance container"
613
+ long_desc <<DESC
614
+ First, a Conjur appliance container is created and started. By default, the
615
+ container image is registry.tld/conjur-appliance-cuke-master. An image tag
616
+ MUST be supplied. This image is configured with all the CONJUR_ environment
617
+ variables setup for the local environment (appliance URL, cert path, admin username and
618
+ password, etc). The project source tree is also mounted into the container, at
619
+ /src/<project-name>, where <project-name> is taken from the name of the current working directory.
620
+
621
+ Once in the container, use "/opt/conjur/evoke/bin/dev-install" to install the development bundle of your project.
622
+ DESC
623
+ command "sandbox" do |c|
624
+ c.desc "Set the current working directory"
625
+ c.flag [:d, :dir]
626
+
627
+ c.desc "Image name"
628
+ c.default_value "registry.tld/conjur-appliance-cuke-master"
629
+ c.flag [:i, :image]
630
+
631
+ c.desc "Image tag, e.g. 4.5-stable, 4.6-stable"
632
+ c.flag [:t, "image-tag"]
633
+
634
+ c.desc "Bind another source directory into the container. Use <src>:<dest>, where both are full paths."
635
+ c.flag [:"bind"], :multiple => true
636
+
637
+ c.desc "'docker pull' the Conjur container image"
638
+ c.default_value false
639
+ c.switch [:pull]
640
+
641
+ network_options(c)
642
+
643
+ c.desc "Specify volume for container"
644
+ c.flag [:'volumes-from'], :multiple => true
645
+
646
+ c.desc "Expose a port from the container to host. Use <host>:<container>."
647
+ c.flag [:p, :port], :multiple => true
648
+
649
+ c.desc 'Run dev-install in /src/<project-name>'
650
+ c.default_value false
651
+ c.switch [:'dev-install']
652
+
653
+ c.desc 'Kill previous sandbox container'
654
+ c.default_value false
655
+ c.switch [:kill]
656
+
657
+ c.desc 'A command to run in the sandbox'
658
+ c.flag [:c, :command]
659
+
660
+ c.action do |global_options, cmd_options, args|
661
+ raise "Received extra command-line arguments" if args.shift
662
+
663
+ dir = cmd_options[:dir] || '.'
664
+ dir = File.expand_path(dir)
665
+
666
+ raise "Directory #{dir} does not exist or is not a directory" unless File.directory?(dir)
667
+
668
+ Dir.chdir dir do
669
+ image_tag = cmd_options["image-tag"] or raise "image-tag is required"
670
+ appliance_image_id = [cmd_options[:image], image_tag].join(":")
671
+
672
+ appliance_image = if cmd_options[:pull]
673
+ begin
674
+ tries ||= 2
675
+ Docker::Image.create 'fromImage' => appliance_image_id, &DebugMixin::DOCKER if cmd_options[:pull]
676
+ rescue
677
+ login_to_registry appliance_image_id
678
+ retry unless (tries -= 1).zero?
679
+ end
680
+ else
681
+ Docker::Image.get appliance_image_id
682
+ end
683
+
684
+ project_name = File.basename(Dir.getwd)
685
+ vendor_dir = File.expand_path("tmp/debify/#{project_name}/vendor", ENV['HOME'])
686
+ dot_bundle_dir = File.expand_path("tmp/debify/#{project_name}/.bundle", ENV['HOME'])
687
+ FileUtils.mkdir_p vendor_dir
688
+ FileUtils.mkdir_p dot_bundle_dir
689
+
690
+ options = {
691
+ 'name' => "#{project_name}-sandbox",
692
+ 'Image' => appliance_image.id,
693
+ 'WorkingDir' => "/src/#{project_name}",
694
+ 'Env' => [
695
+ "CONJUR_AUTHN_LOGIN=admin",
696
+ "CONJUR_ENV=appliance",
697
+ "CONJUR_AUTHN_API_KEY=SEcret12!!!!",
698
+ "CONJUR_ADMIN_PASSWORD=SEcret12!!!!",
699
+ ] + global_options[:env]
700
+ }
701
+
702
+ options['HostConfig'] = host_config = {}
703
+ host_config['Binds'] = [
704
+ [File.expand_path(".ssh/id_rsa", ENV['HOME']), "/root/.ssh/id_rsa", 'ro'].join(':'),
705
+ [dir, "/src/#{project_name}"].join(':'),
706
+ ] + Array(cmd_options[:bind])
707
+
708
+ if global_options[:'local-bundle']
709
+ host_config['Binds']
710
+ .push([vendor_dir, "/src/#{project_name}/vendor"].join(':'))
711
+ .push([dot_bundle_dir, "/src/#{project_name}/.bundle"].join(':'))
712
+ end
713
+
714
+ host_config['Privileged'] = true if Docker.version['Version'] >= '1.10.0'
715
+ host_config['VolumesFrom'] = cmd_options[:'volumes-from'] unless cmd_options[:'volumes-from'].empty?
716
+
717
+ add_network_config(options, cmd_options)
718
+
719
+ unless cmd_options[:port].empty?
720
+ port_bindings = Hash.new({})
721
+ cmd_options[:port].each do |mapping|
722
+ hport, cport = mapping.split(':')
723
+ port_bindings["#{cport}/tcp"] = [{'HostPort' => hport}]
724
+ end
725
+ host_config['PortBindings'] = port_bindings
726
+ end
727
+
728
+ if cmd_options[:kill]
729
+ previous = Docker::Container.get(options['name']) rescue nil
730
+ previous.delete(:force => true) if previous
731
+ end
732
+
733
+ container = Docker::Container.create(options.tap { |o| DebugMixin.debug_write "creating container with options #{o.inspect}" })
734
+ $stdout.puts container.id
735
+ container.start!
736
+
737
+ wait_for_conjur appliance_image, container
738
+
739
+ if cmd_options[:'dev-install']
740
+ container_command(container, "/opt/conjur/evoke/bin/dev-install", project_name)
741
+ container_command(container, 'sv', 'restart', "conjur/#{project_name}")
742
+ end
743
+
744
+ if cmd_options[:command]
745
+ container_command(container, '/bin/bash', '-c', cmd_options[:command])
746
+ end
747
+ end
748
+ end
749
+ end
750
+
751
+ desc "Publish a debian package to apt repository"
752
+ long_desc <<DESC
753
+ Publishes a deb created with `debify package` to our private apt repository.
754
+
755
+ "distribution" should match the major/minor version of the Conjur appliance you want to install to.
756
+
757
+ The package name is a required option. The package version can be specified as a CLI option, or it will
758
+ be auto-detected from Git.
759
+
760
+ --component should be 'stable' if run after package tests pass or 'testing' if the package is not yet ready for release.
761
+ If you don't specify the component, it will be set to 'testing' unless the current git branch is 'master' or 'origin/master'.
762
+ 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`
763
+ (which won't give you the answer you want when detached).
764
+
765
+ DESC
766
+ arg_name "distribution project-name"
767
+ command "publish" do |c|
768
+ c.desc "Set the current working directory"
769
+ c.flag [:d, :dir]
770
+
771
+ c.desc "Specify the deb package version; by default, it's computed automatically"
772
+ c.flag [:v, :version]
773
+
774
+ c.desc "Component to publish to, either 'stable' or the name of the git branch"
775
+ c.flag [:c, :component]
776
+
777
+ c.desc "Artifactory URL to publish to"
778
+ c.default_value "https://conjurinc.jfrog.io/conjurinc"
779
+ c.flag [:u, :url]
780
+
781
+ c.desc "Artifactory Debian repo to publish package to"
782
+ c.default_value "debian-private"
783
+ c.flag [:r, :repo]
784
+
785
+ c.desc "Artifactory RPM repo to publish package to"
786
+ c.default_value "redhat-private"
787
+ c.flag ['rpm-repo']
788
+
789
+ c.action do |global_options, cmd_options, args|
790
+ require 'conjur/debify/action/publish'
791
+ raise "distribution is required" unless distribution = args.shift
792
+ raise "project-name is required" unless project_name = args.shift
793
+ raise "Received extra command-line arguments" if args.shift
794
+
795
+ Conjur::Debify::Action::Publish.new(distribution, project_name, cmd_options).run
796
+ end
797
+ end
798
+
799
+ desc "Auto-detect and print the repository version"
800
+ command "detect-version" do |c|
801
+ c.desc "Set the current working directory"
802
+ c.flag [:d, :dir]
803
+ c.action do |global_options, cmd_options, args|
804
+ raise "Received extra command-line arguments" if args.shift
805
+
806
+ dir = cmd_options[:dir] || '.'
807
+ dir = File.expand_path(dir)
808
+
809
+ raise "Directory #{dir} does not exist or is not a directory" unless File.directory?(dir)
810
+
811
+ Dir.chdir dir do
812
+ puts detect_version
813
+ end
814
+ end
815
+ end
816
+
817
+ desc 'Show the given configuration'
818
+ arg_name 'configuration'
819
+ command 'config' do |c|
820
+ c.action do |_, _, args|
821
+ raise 'no configuration provided' unless config = args.shift
822
+ raise "Received extra command-line arguments" if args.shift
823
+
824
+ File.open(File.join('distrib', config)).each do |line|
825
+ puts line.gsub(/@@DEBIFY_VERSION@@/, Conjur::Debify::VERSION)
826
+ end
827
+ end
828
+ end
829
+
830
+
831
+ pre do |global, command, options, args|
832
+ # Pre logic here
833
+ # Return true to proceed; false to abort and not call the
834
+ # chosen command
835
+ # Use skips_pre before a command to skip this block
836
+ # on that command only
837
+ true
838
+ end
839
+
840
+ post do |global, command, options, args|
841
+ # Post logic here
842
+ # Use skips_post before a command to skip this
843
+ # block on that command only
844
+ end
845
+
846
+ on_error do |exception|
847
+ # Error logic here
848
+ # return false to skip default error handling
849
+ true
850
+ end
851
+