docker-registry-sync 0.1.5 → 0.2.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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MDJmMGE4YzQyMmNjMmE3ZmEzNzI1MTIyZDgyMTgyMDJhOWQ3OGMxOA==
4
+ MjJjMmUyZWRiNWY1NjYzMDI1MTZiMjAxZDZhMWY5MDgzZjNjOTFiOA==
5
5
  data.tar.gz: !binary |-
6
- YWVhNDY4ZTYwYTcwMmRlNWEwODU5ZDYyMzg2ZmMzNDg5ZDVhNmExMQ==
6
+ Nzc5MWQxNzhiZmUxOGU0NTRlNzEyYTU2MmEzODE5ODhiMTE3MDllMw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NzFjNDc1MzFiNjM2ZGQ2ZWRlMDE2OGQyZTcyNjFjMGU3ZjAyNTUyZjQzODQx
10
- YTUxYTQxNmU4NDhjNWFhNmM0NmQ3NDA1YjZjMGQxNDIxMDBkZTYwNDFlNzMw
11
- NTdmYzBiNzViYWU0NzdjODM1NzZkMzU1ZjE3MTc0YWVjMzAwNzQ=
9
+ Yzc0MTExY2M3MjkxYTFlNzQxY2ExYjhiMTg5YTI0ODI1MTY4ZDdkNDA4YmQ2
10
+ MTA3ODNkMzU1ZWExODcyZTViNDExMjgzZGQ4ZjdjMDk1ZDQxNWE1YmFmNzI4
11
+ YWI2ZWVkN2VmM2NlZTYyMWExMmVjZDY3OGJiZTExY2Y3NGRkZjk=
12
12
  data.tar.gz: !binary |-
13
- NGRlODNhMjljYTI1MGNiYTAwMWE5ZWQ2NDU3MGY1YTZkNTFjZjgzNTMwMjg5
14
- Yzc0YmY0MGVkYTNkYmI5MDI2NzVmZjE3MjhjNDNhNTk5YmQ4YzA2ODA5Yjdk
15
- MTIyYjZmNGZhYTI2YmMyNjhhNzczOTZlY2YxZWU2OTU1ZDIyYjU=
13
+ NzIyMzM5OWY2ZDRkYTA2MmZlMDE5Y2ViYTZkYTE1YjE3NWVlMmFhY2RjNzZk
14
+ YTM1YjJhNWQxNzQ2NDEwMjU5ZTY2NjhiZjJmOWMyNWJkYmVmZGQzYzQ5ODAw
15
+ MjJkMmY4ZjZkNmQyZGFkOGFjMTBmYzRkNWEwNWNlOGE2ZDQyODU=
@@ -64,6 +64,12 @@ EOC
64
64
  options[:source_sse] = true
65
65
  end
66
66
 
67
+ options[:pool] = 5
68
+ opts.on('-n POOL', '--pool POOL', "Size of worker thread pool, defaults to 5.") do |t|
69
+ puts "pool: #{t}"
70
+ options[:pool] = t.to_i
71
+ end
72
+
67
73
  options[:unset_proxy] = false
68
74
  opts.on(nil, '--unset-proxy', "Use if 'http_proxy' is set in your environment, but you don't want to use it...") do |_u|
69
75
  options[:unset_proxy] = true
@@ -96,6 +102,7 @@ unless options[:unset_proxy]
96
102
  end
97
103
  end
98
104
 
105
+ puts options, cmd
99
106
  unless verify_opts(options, cmd)
100
107
  puts "Both '--source-bucket' and '--target-buckets' must be defined for 'sync' and 'queue-sync' commands\n"
101
108
  puts "'--queue' must be defined for 'queue-sync' and 'run-sync' commands\n\n"
@@ -108,7 +115,8 @@ CMD.configure(
108
115
  options[:target_buckets],
109
116
  options[:sqs_queue],
110
117
  options[:sse],
111
- options[:source_sse]
118
+ options[:source_sse],
119
+ options[:pool]
112
120
  )
113
121
 
114
122
  ec = 1
@@ -1,4 +1,6 @@
1
1
  require 'json'
2
+ require 'thread'
3
+ require 'monitor'
2
4
  require 'docker/registry/sync/s3'
3
5
  require 'docker/registry/sync/sqs'
4
6
 
@@ -9,7 +11,7 @@ module Docker
9
11
  include Docker::Registry::Sync
10
12
 
11
13
  class << self
12
- def configure(source_bucket, target_buckets, sqs_queue, use_sse, source_uses_sse)
14
+ def configure(source_bucket, target_buckets, sqs_queue, use_sse, source_uses_sse, pool)
13
15
  unless source_bucket.nil?
14
16
  source_region, source_bucket = source_bucket.split(':')
15
17
  else
@@ -27,6 +29,7 @@ module Docker
27
29
  else
28
30
  sqs_region, sqs_uri = nil, nil
29
31
  end
32
+
30
33
  Docker::Registry::Sync.configure do |config|
31
34
  config.source_bucket = source_bucket
32
35
  config.source_region = source_region
@@ -34,6 +37,7 @@ module Docker
34
37
  config.source_sse = source_uses_sse
35
38
  config.sse = use_sse
36
39
  config.sqs_region = sqs_region
40
+ config.pool_size = pool
37
41
  config.sqs_url = "https://#{sqs_uri}"
38
42
  end
39
43
  @config = Docker::Registry::Sync.config
@@ -44,15 +48,53 @@ module Docker
44
48
 
45
49
  Signal.trap('INT') do
46
50
  @config.logger.error 'Received INT signal...'
51
+ @producer_finished = true
47
52
  @terminated = true
48
53
  end
49
54
  Signal.trap('TERM') do
50
55
  @config.logger.error 'Received TERM signal...'
56
+ @producer_finished = true
51
57
  @terminated = true
52
58
  end
53
59
  end
54
60
 
61
+ def start_workers
62
+ @threads = Array.new(@config.pool_size)
63
+ @work_queue = Queue.new
64
+ @status_queue = Queue.new
65
+
66
+ @threads.extend(MonitorMixin)
67
+ @threads_available = @threads.new_cond
68
+
69
+ @producer_finished = false
70
+ @consumer_thread = Thread.new do
71
+ sync_key_consumer
72
+ end
73
+ end
74
+
75
+ def finalize_workers
76
+ return true
77
+ @producer_finished = true
78
+ @consumer_thread.join
79
+ @threads.each { |t| t.join unless t.nil? }
80
+ @config.logger.info "Processing job results..."
81
+
82
+ success = true
83
+ loop do
84
+ begin
85
+ # One job filure is a run failure
86
+ success &&= @status_queue.pop(true)
87
+ rescue ThreadError
88
+ @config.logger.info "Finished processing job results..."
89
+ break
90
+ end
91
+ end
92
+ success && !@terminated
93
+ end
94
+
55
95
  def sync(image, tag)
96
+ configure_signal_handlers
97
+ start_workers
56
98
  success = false
57
99
  @config.target_buckets.each do |region, bucket, sse|
58
100
  if image_exists?(image, bucket, region)
@@ -61,6 +103,7 @@ module Docker
61
103
  success = sync_repo(image, bucket, region, !sse.nil?)
62
104
  end
63
105
  end
106
+ success &&= finalize_workers
64
107
  success ? 0 : 1
65
108
  end
66
109
 
@@ -85,13 +128,15 @@ module Docker
85
128
  def run_sync
86
129
  ec = 1
87
130
  configure_signal_handlers
131
+ start_workers
88
132
  begin
89
133
  @config.logger.info 'Polling queue for images to sync...'
90
134
  sqs = Aws::SQS::Client.new(region: @config.sqs_region)
91
135
  resp = sqs.receive_message(
92
136
  queue_url: @config.sqs_url,
93
137
  max_number_of_messages: 1,
94
- visibility_timeout: 900, # Give ourselves 15min to sync the image
138
+ #visibility_timeout: 900, # Give ourselves 15min to sync the image
139
+ visibility_timeout: 30, # Give ourselves 15min to sync the image
95
140
  wait_time_seconds: 10, # Wait a maximum of 10s for a new message
96
141
  )
97
142
  @config.logger.info "SQS returned #{resp.messages.length} new images to sync..."
@@ -102,15 +147,20 @@ module Docker
102
147
 
103
148
  if image_exists?(data['image'], data['target']['bucket'], data['target']['region'])
104
149
  @config.logger.info("Syncing tag: #{data['image']}:#{data['tag']} to #{data['target']['region']}:#{data['target']['bucket']}")
105
- if sync_tag(data['image'], data['tag'], data['target']['bucket'], data['target']['region'], data['target']['sse'], data['source']['bucket'], data['source']['region'])
150
+ success = sync_tag(data['image'], data['tag'], data['target']['bucket'], data['target']['region'], data['target']['sse'], data['source']['bucket'], data['source']['region'])
151
+ success &&= finalize_workers
152
+
153
+ if success
106
154
  @config.logger.info("Finished syncing tag: #{data['image']}:#{data['tag']} to #{data['target']['region']}:#{data['target']['bucket']}")
107
155
  finalize_message(message.receipt_handle)
108
156
  else
109
157
  @config.logger.info("Falied to sync tag, leaving on queue: #{data['image']}:#{data['tag']} to #{data['target']['region']}:#{data['target']['bucket']}")
110
158
  end
111
159
  else
160
+ success = sync_repo(data['image'], data['target']['bucket'], data['target']['region'], data['target']['sse'], data['source']['bucket'], data['source']['region'])
161
+ success &&= finalize_workers
112
162
  @config.logger.info("Syncing image: #{data['image']} to #{data['target']['region']}:#{data['target']['bucket']}")
113
- if sync_repo(data['image'], data['target']['bucket'], data['target']['region'], data['target']['sse'], data['source']['bucket'], data['source']['region'])
163
+ if success
114
164
  @config.logger.info("Finished syncing image: #{data['image']} to #{data['target']['region']}:#{data['target']['bucket']}")
115
165
  finalize_message(message.receipt_handle)
116
166
  else
@@ -53,7 +53,7 @@ module Docker
53
53
  end
54
54
 
55
55
  class Configuration
56
- attr_accessor :source_bucket, :source_region, :target_buckets, :sqs_region, :sqs_url, :empty_queue_sleep_time, :sse, :source_sse
56
+ attr_accessor :source_bucket, :source_region, :target_buckets, :sqs_region, :sqs_url, :empty_queue_sleep_time, :sse, :source_sse, :pool_size
57
57
  attr_accessor :log_level, :logger
58
58
 
59
59
  def initialize
@@ -69,6 +69,7 @@ module Docker
69
69
  @sse = false
70
70
 
71
71
  @empty_queue_sleep_time = 5
72
+ @pool_size = 5
72
73
 
73
74
  @log_level = :debug
74
75
  @logger = Docker::Registry::Sync::STDLogger
@@ -36,6 +36,7 @@ module Docker
36
36
  sync_image(img_id, bucket, region, sse, source_bucket, source_region)
37
37
  rescue Exception => e
38
38
  @config.logger.error "An unexpected error occoured while syncing tag #{image}:#{tag}: #{e}"
39
+ @config.logger.error e.backtrace
39
40
  false
40
41
  else
41
42
  true
@@ -58,6 +59,7 @@ module Docker
58
59
  end
59
60
  rescue Exception => e
60
61
  @config.logger.error "An unexpected error occoured while syncing repo #{repo}: #{e}"
62
+ @config.logger.error e.backtrace
61
63
  false
62
64
  else
63
65
  true
@@ -99,6 +101,7 @@ module Docker
99
101
  keys.each do |key|
100
102
  @config.logger.info "Syncing key #{source_bucket}/#{key} to bucket #{target_bucket}"
101
103
  opts = {acl: 'bucket-owner-full-control',
104
+ region: target_client.get_bucket_location(bucket: target_bucket).location_constraint,
102
105
  bucket: target_bucket,
103
106
  key: key,
104
107
  copy_source: "#{source_bucket}/#{key}"}
@@ -108,7 +111,52 @@ module Docker
108
111
  if @config.source_sse
109
112
  opts[:copy_source_sse_customer_algorithm] = 'AES256'
110
113
  end
111
- target_client.copy_object(opts)
114
+ @work_queue << opts
115
+ sleep 0.1
116
+ end
117
+ end
118
+
119
+ def sync_key_consumer
120
+ @config.logger.info "Starting sync consumer..."
121
+ loop do
122
+ break if @producer_finished && @work_queue.length == 0
123
+ t_index = nil
124
+
125
+ begin
126
+ busy = @threads.select { |t| t.nil? || t.status == false || t['finished'].nil? == false }.length == 0
127
+ end until !busy
128
+ t_index = @threads.rindex { |t| t.nil? || t.status == false || t['finished'].nil? == false }
129
+
130
+ begin
131
+ opts = @work_queue.pop(true)
132
+ rescue ThreadError
133
+ @config.logger.info "No work found on the queue, sleeping..."
134
+ sleep 1
135
+ else
136
+ if opts[:key]
137
+ @threads[t_index].join unless @threads[t_index].nil?
138
+ @threads[t_index] = Thread.new do
139
+ @config.logger.info "Worker syncing key: #{opts[:key]}"
140
+ target_client = Aws::S3::Client.new(region: opts[:region])
141
+ opts.delete :region
142
+ success = false
143
+ begin
144
+ target_client.copy_object(opts)
145
+ success = true
146
+ rescue Exception => e
147
+ @config.logger.error "An unknown error occoured while copying object in s3: #{e}"
148
+ @config.logger.error e.backtrace
149
+ end
150
+ Thread.current['finished'] = true
151
+ @threads.synchronize do
152
+ @status_queue << success
153
+ end
154
+ @config.logger.info "Worker finished syncing key: #{opts[:key]}"
155
+ end
156
+ else
157
+ @config.logger.info "Queued work empty: #{opts}"
158
+ end
159
+ end
112
160
  end
113
161
  end
114
162
  end
@@ -1,7 +1,7 @@
1
1
  module Docker
2
2
  module Registry
3
3
  module Sync
4
- VERSION = '0.1.5'
4
+ VERSION = '0.2.0'
5
5
  end
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docker-registry-sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Oldfield
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-04 00:00:00.000000000 Z
11
+ date: 2015-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler