aggkit 0.2.6.8250 → 0.2.6.8329

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,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed42b42b8001ba82d6139bf6d94598e86bc87f34
4
- data.tar.gz: 4ad91cf44ecd2e40a5fc4f7aeae3757c89a20171
3
+ metadata.gz: dda760666b51369b116244aa6cc3ead5f69afb41
4
+ data.tar.gz: 3df47be27d64b70300707667651893475a30ff92
5
5
  SHA512:
6
- metadata.gz: c2cef1a46a08ea9a5f432d31324f2f422adaaa955a7abfe5e7265f19783d8741a63f7697adb499e241f00c991c0e5edee50747ec774d173ecbb27c9535023950
7
- data.tar.gz: ab2a09f904b76b1229e2a7e2ecfa16e6dddef79b4d758ea51efd625dd8222f40e565e336c55e1fb8769d366b791dd5d8869f926a0905a64cb7ea1dd168c2dd53
6
+ metadata.gz: 63d4ab22451f3cc95e16653f49776cba230e194e8f580bc54828a7e10037ab761352e1dbfa7adccdf35cec1acd0a34586c04c0049961de28045e40584b17928b
7
+ data.tar.gz: a820f88d3be11610a7f8bff76c1c387c334d5892eede30c96f6a146071d5ac8e859eec9a114af9982a1ab1a7ac7930a17dc3edab17c3daaa7df731b6fa558641
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- aggkit (0.2.6.8250)
4
+ aggkit (0.2.6.8329)
5
5
  diplomat
6
6
  dotenv
7
7
  json
data/aggkit.gemspec CHANGED
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency 'dotenv'
32
32
  spec.add_dependency 'json'
33
33
  spec.add_dependency 'tty-tree'
34
+
34
35
 
35
36
 
36
37
  spec.add_development_dependency 'bundler', '~> 1.14'
data/bin/agg CHANGED
@@ -5,6 +5,8 @@ require 'tty-tree'
5
5
  require 'aggkit/env'
6
6
  require 'json'
7
7
 
8
+ UTIL = File.basename(__FILE__)
9
+
8
10
  @commands = {}
9
11
 
10
12
  @exec = (begin
@@ -20,13 +22,13 @@ require 'json'
20
22
 
21
23
 
22
24
  exmaple_usage = "
23
- #{'agg env --list'.ljust(30)} - list avaibale environments
24
- #{'agg <envname> --show'.ljust(30)} - show environment details
25
- #{'agg <envname> exec -- cmd'.ljust(30)} - exec command in environment
25
+ #{"#{UTIL} env --list".ljust(30)} - list avaibale environments
26
+ #{"#{UTIL} <envname> --show".ljust(30)} - show environment details
27
+ #{"#{UTIL} <envname> exec -- cmd".ljust(30)} - exec command in environment
26
28
  "
27
29
 
28
30
  env_cmd_parser = OptionParser.new do |o|
29
- o.banner = "Usage: agg env [options]"
31
+ o.banner = "Usage: #{UTIL} env [options]"
30
32
  o.on("-l", "--list", "list environments") do
31
33
  @opts[:list] = true
32
34
  end
@@ -36,7 +38,7 @@ env_cmd_parser = OptionParser.new do |o|
36
38
  end
37
39
 
38
40
  exec_cmd_parser = OptionParser.new do |o|
39
- o.banner = "Usage: agg <envname> [options] exec [options] -- cmd"
41
+ o.banner = "Usage: #{UTIL} <envname> [options] exec [options] -- cmd"
40
42
  o.on('-p', '--pristine', "not include the parent processes' environment when exec child process") do
41
43
  @opts[:pristine] = true
42
44
  end
@@ -54,7 +56,7 @@ end
54
56
  }
55
57
 
56
58
  single_parser = OptionParser.new do |o|
57
- o.banner = "Aggredator environment manager\nagg <envname> [options] [subcommand [options]]"
59
+ o.banner = "Aggredator environment manager\nUsage: #{UTIL} <envname> [options] [subcommand [options]]"
58
60
 
59
61
  o.on("-s", "--show", "show envname details") do
60
62
  @opts[:show] = true
@@ -64,6 +66,10 @@ single_parser = OptionParser.new do |o|
64
66
  @opts[:export] = true
65
67
  end
66
68
 
69
+ o.on("-r", "--release", "make release compose-result.yml(without build)") do
70
+ @opts[:release] = true
71
+ end
72
+
67
73
  o.on("-h", "--help", "show help") do
68
74
  puts o.help
69
75
  exit 0
@@ -74,7 +80,7 @@ single_parser = OptionParser.new do |o|
74
80
  end
75
81
 
76
82
  global_parser = OptionParser.new do |o|
77
- o.banner = "Aggredator manager\nUsage: agg [envname] [options] [subcommand [options]]"
83
+ o.banner = "Aggredator manager\nUsage: #{UTIL} [envname] [options] [subcommand [options]]"
78
84
 
79
85
  o.on("-h", "--help", "show help") do
80
86
  puts o.help
@@ -144,6 +150,7 @@ def exec_command opts
144
150
  raise "envname not specified!" unless $env
145
151
  raise "cmd not specified!" unless @exec
146
152
 
153
+ $env.prepare release: @opts[:release]
147
154
  e = $env.environment
148
155
  e = ENV.to_h.merge(e) unless opts[:pristine]
149
156
 
@@ -161,6 +168,7 @@ if @commands[:single]
161
168
  if @opts[:show]
162
169
  raise "envname not specified!" unless $env
163
170
 
171
+ $env.prepare release: @opts[:release]
164
172
  puts JSON.pretty_generate($env.show)
165
173
  exit 0
166
174
  end
@@ -168,6 +176,7 @@ if @commands[:single]
168
176
  if @opts[:export]
169
177
  raise "envname not specified!" unless $env
170
178
 
179
+ $env.prepare release: @opts[:release]
171
180
  $env.environment.each_pair do |k, v|
172
181
  puts "export #{k.to_s}=\"#{v.to_s}\""
173
182
  end
data/bin/aggci ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'aggkit/ci'
5
+
6
+ UTIL = File.basename(__FILE__)
7
+
8
+ include Aggkit::Ci
9
+
10
+ @opts = {
11
+ tag: ENV['CORE_TAG'] || ENV['CI_COMMIT_TAG'] || 'latest'
12
+ }
13
+
14
+ def parse_services services
15
+ [services].flatten.join(' ').split(/[,;| \n]/).map(&:strip).reject(&:empty?).uniq.sort
16
+ end
17
+
18
+ parser = OptionParser.new do |o|
19
+ o.banner = "Usage: #{UTIL} [options]"
20
+
21
+ o.on("--pull-cache services", 'Pull cache from docker regestry') do |services|
22
+ @opts[:pull] = parse_services(services)
23
+ end
24
+
25
+ o.on("--promote services", 'Promote service images to --stage') do |services|
26
+ @opts[:promote] = parse_services(services)
27
+ end
28
+
29
+ o.on("--promote-tag tag", 'Promote service images') do |tag|
30
+ @opts[:promotetag] = tag.to_s.strip
31
+ end
32
+
33
+ o.on("--stage stage", 'stage for image promotion Stages: dev, stge, tag') do |stage|
34
+ @opts[:stage] = stage.to_s.strip
35
+ end
36
+
37
+ o.on("--clean services", 'remove tags and images excluding last stable') do |services|
38
+ @opts[:clean] = parse_services(services)
39
+ end
40
+
41
+ o.on("--ssh-deploy env", 'ssh') do |env_and_server|
42
+ env, server = env_and_server.strip.split('@')
43
+ @opts[:deploy] = {env: env, server: server}
44
+ end
45
+
46
+ o.on("--tag tag=#{@opts[:tag].inspect}", 'Use tag as default tag for images') do |tag|
47
+ @opts[:tag] = tag.to_s.strip
48
+ end
49
+
50
+ end
51
+ parser.parse!
52
+
53
+ if services = @opts[:pull]
54
+ raise "tag not defined" if @opts[:tag].to_s.empty?
55
+ pull_images_cache services, @opts[:tag]
56
+ exit 0
57
+ elsif services = @opts[:promote]
58
+ raise "tag not defined" if @opts[:tag].to_s.empty?
59
+ raise "stage not defined" if @opts[:stage].to_s.empty?
60
+
61
+ case @opts[:stage]
62
+ when 'dev'
63
+ promote_services_to_dev!(services, @opts[:tag])
64
+ when 'stage'
65
+ promote_services_to_stage!(services, @opts[:tag])
66
+ when 'tag'
67
+ raise "promote-tag not defined" if @opts[:promotetag].to_s.empty?
68
+ promote_services_to_tag!(services, @opts[:tag], @opts[:promotetag])
69
+ else
70
+ raise "invalid stage #{@opts[:stage].inspect} for promotion"
71
+ end
72
+
73
+ exit 0
74
+
75
+ elsif services = @opts[:clean]
76
+ raise "tag not defined" if @opts[:tag].to_s.empty?
77
+ clean_services services, @opts[:tag]
78
+ exit 0
79
+
80
+ elsif deploy = @opts[:deploy]
81
+ raise "tag not defined" if @opts[:tag].to_s.empty?
82
+ raise "stage not defined" if @opts[:stage].to_s.empty?
83
+ ssh_deploy 'service-fns', deploy[:env], deploy[:server], @opts[:tag], @opts[:stage]
84
+ exit 0
85
+ end
86
+
87
+
88
+ STDERR.puts parser.help
89
+ exit 1
90
+
data/bin/aggmerge CHANGED
@@ -11,6 +11,8 @@ if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
11
11
  end
12
12
 
13
13
 
14
+ @skipbuild = ARGV.join('')['--skip-build']
15
+
14
16
  class Hash
15
17
 
16
18
  def deep_dup
@@ -99,6 +101,23 @@ def process_compose_hash(yml, dirname, parent = {})
99
101
  yml
100
102
  end
101
103
 
104
+ def remove_build_section hash
105
+ if hash.has_key?(:build) || hash.has_key?('build')
106
+ hash.delete(:build)
107
+ hash.delete('build')
108
+ end
109
+
110
+ hash.each_pair do |k, v|
111
+ if v.is_a?(Hash)
112
+ remove_build_section(v)
113
+ elsif v.is_a?(Array)
114
+ v.each do |vv|
115
+ remove_build_section(vv) if vv.is_a?(Hash)
116
+ end
117
+ end
118
+ end
119
+ end
120
+
102
121
  if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
103
122
  result = ENV['COMPOSE_FILE'].split(':').reduce({}) do |parent, file|
104
123
  yml = process_compose_hash(YAML.load(File.read(file)), File.dirname(file), parent)
@@ -109,6 +128,9 @@ if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
109
128
  ret
110
129
  end
111
130
 
131
+
132
+ remove_build_section(result) if @skipbuild
133
+
112
134
  if ARGV[0].nil? || ARGV[0].strip == '-'
113
135
  puts YAML.dump(result)
114
136
  else
File without changes
data/docker/run_tests.sh CHANGED
@@ -2,6 +2,8 @@
2
2
  SELF=$(readlink -f $0)
3
3
  export ENV_ROOT=$(dirname "${SELF}")
4
4
 
5
+ export COMPOSE_PROJECT_NAME=agg-aggkit-${CI_JOB_ID}
6
+
5
7
  trap '$ENV_ROOT/down.sh' EXIT
6
8
 
7
9
  cd $ENV_ROOT
data/lib/aggkit/ci.rb ADDED
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'English'
4
+ require 'tempfile'
5
+ require 'json'
6
+ require 'aggkit/env'
7
+
8
+ module Aggkit
9
+
10
+ module Ci
11
+
12
+ def log_action action
13
+ puts " => #{action}"
14
+ end
15
+
16
+ def info message
17
+ puts " => #{message}"
18
+ end
19
+
20
+ def execute cmd
21
+ puts " - executing: #{cmd}"
22
+ result = `#{cmd}`
23
+ puts result
24
+ if !$?.success?
25
+ raise "Execution failed cmd: #{cmd.inspect}"
26
+ end
27
+ result
28
+ end
29
+
30
+ #Уровни образов
31
+ # 1. Без модификатора - не протестирован, отсутсвует в репозитории.
32
+ # 2. С модификатором dev - протестирован автотестами, ДОЛЖЕН быть в репозитории.
33
+ # 3. С модификатором stage - протестирован руками, ДОЛЖЕН быть в репозитории(как и его предшественник dev).
34
+ # 4. С модификатором release - помечен для релиза, ДОЛЖЕН быть в репозитории(как и его предшественник dev и stage).
35
+
36
+ #Работа должна вестить ТОЛЬКО с workimage
37
+
38
+ # Базовое имя образа
39
+ # Например docker.rnds.pro/aggredator-mq
40
+ def baseimage service
41
+ raise "Service name invalid: #{service.inspect}" if service.to_s.length < 2
42
+ "docker.rnds.pro/aggredator-#{service}"
43
+ end
44
+
45
+
46
+ # Текущее имя образа без модификаторов продвижения, но с тегом
47
+ # Например docker.rnds.pro/aggredator-mq:7e6420b5
48
+ def workimage service, tag
49
+ raise "Service tag invalid: #{tag.inspect}" if tag.to_s.length < 2
50
+ "#{baseimage(service)}:#{tag}"
51
+ end
52
+
53
+ # Образ с модификатором версии
54
+ # Например docker.rnds.pro/aggredator-mq-dev:7e6420b5
55
+ def promotedimage service, stage, tag
56
+ raise "Service stage invalid: #{stage.inspect}" if stage.to_s.length < 2
57
+ raise "Service tag invalid: #{tag.inspect}" if tag.to_s.length < 2
58
+ "#{baseimage(service)}-#{stage}:#{tag}"
59
+ end
60
+
61
+ # Образ с модификатором протестированной автотестами версии
62
+ # Например docker.rnds.pro/aggredator-mq-dev:7e6420b5
63
+ def devimage service, tag
64
+ promotedimage(service, 'dev', tag)
65
+ end
66
+
67
+ # Образ с модификатором стабильной версии
68
+ # Например docker.rnds.pro/aggredator-mq-stage:7e6420b5
69
+ def stageimage service, tag
70
+ promotedimage(service, 'stage', tag)
71
+ end
72
+
73
+ # Образ с модификатором релизной версии
74
+ #Например docker.rnds.pro/aggredator-mq-release:7e6420b5
75
+ def releaseimage service, tag
76
+ promotedimage(service, 'release', tag)
77
+ end
78
+
79
+ # Протегировать образ тегом
80
+ def tagimage! image, newimage, push: false, ignoreerror: false
81
+ log_action "Tag #{image.inspect} as #{newimage.inspect}"
82
+
83
+ if ignoreerror
84
+ execute("docker tag #{image} #{newimage} 2> /dev/null || true")
85
+ else
86
+ execute("docker tag #{image} #{newimage}")
87
+ end
88
+
89
+ if push
90
+ execute("docker push #{newimage}")
91
+ end
92
+
93
+ return newimage
94
+ end
95
+
96
+ #Удалить образ образ тегом
97
+ def rmimage! image
98
+ log_action "Remove #{image.inspect}"
99
+ execute("docker rmi #{image} &> /dev/null || true")
100
+ end
101
+
102
+ def label! image, label, value
103
+ raise "Image label invalid: #{label.inspect}" if label.to_s.length < 2
104
+ raise "Image label value invalid: #{value.inspect}" if value.to_s.length < 2
105
+
106
+ execute("echo #{image} | docker build -t #{image} --label=\"#{label.to_s}=#{value.to_s}\" - ")
107
+ end
108
+
109
+ # Растегировать все модификатора образа сервиса и возможно удалить его
110
+ def untagservice service, tag
111
+ if tag != 'latest'
112
+ rmimage! workimage(service, tag)
113
+ rmimage! devimage(service, tag)
114
+ rmimage! stageimage(service, tag)
115
+ rmimage! releaseimage(service, tag)
116
+ end
117
+ end
118
+
119
+ # Продвинуть текущий образ сервиса по уровню - добавить модификатор и залить в репозиторий
120
+ def promote! service, tag, stage, fromstage: nil
121
+ fromimage = if fromstage
122
+ promotedimage(service, fromstage, tag)
123
+ else
124
+ workimage(service, tag)
125
+ end
126
+
127
+ toimage = promotedimage(service, stage, tag)
128
+
129
+ log_action "Promote #{fromimage} to #{toimage}"
130
+ return tagimage! fromimage, toimage, push: true
131
+ end
132
+
133
+ # Продвинуть текущий образ по уровню - добавить модификатор dev(протестировано) и залить в репозиторий
134
+ def promote_to_dev! service, tag
135
+ promote!(service, tag, 'dev')
136
+ end
137
+
138
+ # Продвинуть текущий образ по уровню - добавить модификатор stage(стабильно) и залить в репозиторий
139
+ def promote_to_stage! service, tag
140
+ promoted = tagimage!(devimage(service, tag), stageimage(service, tag), push: true)
141
+ # Тегируем этот образ как latest
142
+ return tagimage!(promoted, stageimage(service, 'latest'), push: true)
143
+ end
144
+
145
+ # Продвинуть стабильный образ до указанного тега
146
+ def promote_to_tag! service, tag, newtag
147
+ return tagimage!(devimage(service, tag), stageimage(service, newtag), push: true)
148
+ end
149
+
150
+ # Продвинуть список образов или упасть с ошибкой
151
+ def promote_services_to_dev! services, tag
152
+ [services].flatten.each do |service|
153
+ promote_to_dev!(service, tag)
154
+ end
155
+ end
156
+
157
+ # Продвинуть список образов или упасть с ошибкой
158
+ def promote_services_to_stage! services, tag
159
+ [services].flatten.each do |service|
160
+ promote_to_stage!(service, tag)
161
+ end
162
+ end
163
+
164
+ #Протегировать образы до указанного тега
165
+ def promote_services_to_tag! services, tag, newtag
166
+ [services].flatten.each do |service|
167
+ promote_to_tag!(service, tag, newtag)
168
+ end
169
+ end
170
+
171
+
172
+
173
+ # Попытаться взять все модификаторы образов из репозитория
174
+ # Скачивает образы и тегирует их как workimage
175
+ def pull_images_cache services, tag
176
+ log_action "Puling images#{[services].flatten.inspect} from cache...."
177
+ [services].flatten.each do |service|
178
+ execute("docker pull #{devimage(service, tag)} 2> /dev/null || true")
179
+ execute("docker pull #{stageimage(service, tag)} 2> /dev/null || true")
180
+ execute("docker pull #{releaseimage(service, tag)} 2> /dev/null || true")
181
+ tagimage! devimage(service, tag), workimage(service, tag), ignoreerror: true
182
+ tagimage! stageimage(service, tag), workimage(service, tag), ignoreerror: true
183
+ tagimage! releaseimage(service, tag), workimage(service, tag), ignoreerror: true
184
+ end
185
+ end
186
+
187
+ #Растегировать все образы для дальнейшего удаления. Помечает как latest для поддержки кеширования docker
188
+ def clean_services services, tag
189
+ [services].flatten.each do |service|
190
+ tagimage! workimage(service, tag), workimage(service, 'latest'), ignoreerror: true
191
+ tagimage! devimage(service, tag), workimage(service, 'latest'), ignoreerror: true
192
+ tagimage! stageimage(service, tag), workimage(service, 'latest'), ignoreerror: true
193
+ tagimage! releaseimage(service, tag), workimage(service, 'latest'), ignoreerror: true
194
+ untagservice(service, tag)
195
+ end
196
+ end
197
+
198
+ def ssh_deploy services, env, sshserver, tag, stage
199
+ services = [services].flatten
200
+ log_action "Deploy [#{services.inspect}] tag:#{tag.inspect} stage:#{stage.inspect} to #{sshserver.inspect}"
201
+ with_ssh sshserver do |sockname|
202
+ env_config = JSON(`agg #{env} --show --release`)
203
+ envs = env_config['envs']
204
+ config_file = env_config['config_file'].to_s
205
+ envs['DOCKER_HOST'] = "unix://#{sockname}"
206
+ envs['CORE_TAG'] = tag
207
+ envs['CORE_SUFFIX'] = "-#{stage}"
208
+
209
+ localid = execute("docker info | grep ID").strip
210
+
211
+ Aggkit::Env.with_env(envs) do
212
+ remoteid = execute("docker info | grep ID").strip
213
+
214
+ if localid == remoteid
215
+ raise "Can't get access to remote Docker"
216
+ end
217
+
218
+ execute('docker-compose ps')
219
+ execute('docker-compose pull')
220
+
221
+ consul = execute('docker ps | grep consul').split(' ').first.strip
222
+ puts "CONSULT ID: #{consul}"
223
+
224
+ if !config_file.empty?
225
+ execute("docker exec -i #{consul} waiter.rb -t 20 --consul")
226
+ execute("docker exec -i #{consul} consul.rb --init --override --config - < #{config_file}")
227
+ end
228
+
229
+ execute('docker-compose up -d')
230
+
231
+ services.each do |service|
232
+ deployed = promotedimage(service, stage, tag)
233
+ current = promotedimage(service, stage, 'current')
234
+ tagimage! deployed, current
235
+ untagservice(service, tag)
236
+ end
237
+
238
+ end
239
+ end
240
+ end
241
+
242
+ def with_ssh server, user: 'root', &block
243
+ file = Tempfile.new
244
+ @ssh_sockname = file.path
245
+ file.unlink
246
+ @ssh_pid = spawn("ssh -NL #{@ssh_sockname}:/var/run/docker.sock root@#{server}")
247
+ sleep 5
248
+ yield(@ssh_sockname)
249
+ ensure
250
+ Process.kill(:SIGTERM, @ssh_pid)
251
+ sleep 5
252
+ Process.wait2(@ssh_pid, Process::WNOHANG)
253
+ FileUtils.rm_f(@ssh_sockname)
254
+ end
255
+
256
+ end
257
+
258
+ end
data/lib/aggkit/env.rb CHANGED
@@ -55,8 +55,10 @@ module Aggkit
55
55
  end
56
56
 
57
57
  @env_root = File.join(project_root, 'envs', @name)
58
+ end
58
59
 
59
- @environment = prepare_environment
60
+ def prepare release: false
61
+ @environment = prepare_environment(release: release)
60
62
  end
61
63
 
62
64
  def with_env &block
@@ -87,12 +89,17 @@ module Aggkit
87
89
  [core_dot_file, project_dot_file, dot_file].compact.select{|f| File.exists?(f)}.uniq
88
90
  end
89
91
 
92
+ def config_file
93
+ [File.join(env_root, 'config.yml'), File.join(env_root, 'config.yaml')].compact.select{|f| File.exists?(f)}.uniq.first
94
+ end
95
+
90
96
  def show
91
97
  {
92
98
  name: name,
93
99
  project_root: project_root,
94
100
  core_root: core_root,
95
101
  envfiles: env_files,
102
+ config_file: config_file,
96
103
  envs: environment,
97
104
  }
98
105
  end
@@ -100,7 +107,7 @@ module Aggkit
100
107
  private
101
108
 
102
109
 
103
- def prepare_environment
110
+ def prepare_environment release: false
104
111
  base_env = {
105
112
  'ENV_ROOT' => env_root,
106
113
  'PROJECT_ROOT' => project_root,
@@ -110,7 +117,7 @@ private
110
117
 
111
118
  dot_env = prepare_dot(base_env)
112
119
 
113
- compose_env = prepare_compose(dot_env.merge(base_env))
120
+ compose_env = prepare_compose(dot_env.merge(base_env), release: release)
114
121
 
115
122
  dot_env.merge(base_env).merge(compose_env)
116
123
  end
@@ -121,7 +128,7 @@ private
121
128
  end
122
129
  end
123
130
 
124
- def prepare_compose env
131
+ def prepare_compose env, release: false
125
132
  compose_files = if env['COMPOSE_FILE']
126
133
  env['COMPOSE_FILE']
127
134
  elsif File.exists?(File.join(env_root, "docker-compose.yml"))
@@ -133,8 +140,11 @@ private
133
140
  if compose_files
134
141
  result_file = File.join(env_root, "compose-result.yml")
135
142
  Env.with_env(env.merge('COMPOSE_FILE' => compose_files)) do
136
- result_content = `merger.rb`
137
- File.write(result_file, result_content)
143
+ if release
144
+ `aggmerge #{result_file} --skip-build`
145
+ else
146
+ `aggmerge #{result_file}`
147
+ end
138
148
  end
139
149
  {
140
150
  'COMPOSE_FILE' => result_file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aggkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6.8250
4
+ version: 0.2.6.8329
5
5
  platform: ruby
6
6
  authors:
7
7
  - Godko Ivan
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-02-06 00:00:00.000000000 Z
12
+ date: 2019-02-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: diplomat
@@ -101,15 +101,12 @@ email:
101
101
  - kinnalru@gmail.com
102
102
  executables:
103
103
  - agg
104
+ - aggci
104
105
  - aggconsul
105
106
  - agglock
106
107
  - aggmerge
108
+ - aggterm
107
109
  - aggwait
108
- - consul.rb
109
- - locker.rb
110
- - merger.rb
111
- - terminator.rb
112
- - waiter.rb
113
110
  extensions: []
114
111
  extra_rdoc_files: []
115
112
  files:
@@ -124,15 +121,12 @@ files:
124
121
  - README.md
125
122
  - aggkit.gemspec
126
123
  - bin/agg
124
+ - bin/aggci
127
125
  - bin/aggconsul
128
126
  - bin/agglock
129
127
  - bin/aggmerge
128
+ - bin/aggterm
130
129
  - bin/aggwait
131
- - bin/consul.rb
132
- - bin/locker.rb
133
- - bin/merger.rb
134
- - bin/terminator.rb
135
- - bin/waiter.rb
136
130
  - docker/Dockerfile
137
131
  - docker/docker-compose.yml
138
132
  - docker/down.sh
@@ -165,6 +159,7 @@ files:
165
159
  - lib/aggkit/childprocess/windows/process.rb
166
160
  - lib/aggkit/childprocess/windows/process_builder.rb
167
161
  - lib/aggkit/childprocess/windows/structs.rb
162
+ - lib/aggkit/ci.rb
168
163
  - lib/aggkit/env.rb
169
164
  - lib/aggkit/runner.rb
170
165
  - lib/aggkit/version.rb
data/bin/consul.rb DELETED
@@ -1,222 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'diplomat'
4
- require 'optparse'
5
- require 'English'
6
- require 'yaml'
7
-
8
- STDOUT.sync = true
9
- STDERR.sync = true
10
-
11
- @opts = {}
12
-
13
- @opts[:exec] = (begin
14
- ARGV.join(' ').split(' -- ')[1].strip
15
- rescue StandardError
16
- nil
17
- end)
18
-
19
- parser = OptionParser.new do |o|
20
- o.banner = 'Usage: consul.rb [options] -- exec'
21
-
22
- o.on('--consul url', 'Set up a custom Consul URL') do |url|
23
- Diplomat.configure do |config|
24
- config.url = url.strip
25
- end
26
- end
27
-
28
- o.on('--token token', 'Connect into consul with custom access token (ACL)') do |token|
29
- Diplomat.configure do |config|
30
- config.acl_token = token.strip
31
- end
32
- end
33
-
34
- o.on('--init [service]', 'Initialize Consul services from config') do |service|
35
- @opts[:service] = service
36
- @opts[:init] = true
37
- end
38
-
39
- o.on('--config file', 'Read service configulation from file') do |file|
40
- @opts[:config] = file.strip
41
- end
42
-
43
- o.on('--upload', 'Upload files to variables') do
44
- @opts[:upload] = true
45
- end
46
-
47
- o.on('--show [service]', 'Show service configulation from Consul') do |service|
48
- @opts[:service] = service
49
- @opts[:show] = true
50
- end
51
-
52
- o.on('--override', 'override existed keys') do
53
- @opts[:override] = true
54
- end
55
-
56
- o.on('-d', '--dereference', 'dereference consul values in form of "consul://key/subkey"') do
57
- @opts[:dereference] = true
58
- end
59
-
60
- o.on('--env prefix', 'export KV values from prefix as env varaibles') do |prefix|
61
- @opts[:env] = (prefix + '/').gsub('//', '/')
62
- end
63
-
64
- o.on('--export', 'add export to --env output') do
65
- @opts[:export] = true
66
- end
67
-
68
- o.on('--pristine', "not include the parent processes' environment when exec child process") do
69
- @opts[:pristine] = true
70
- end
71
-
72
- o.on('--put path:value', 'put value to path') do |path|
73
- @opts[:put] = path.strip
74
- end
75
-
76
- o.on('--get path', 'get value from') do |path|
77
- @opts[:get] = path.strip
78
- end
79
- end
80
- parser.parse!
81
-
82
- def die(message)
83
- STDERR.puts "Error: #{message}"
84
- exit 1
85
- end
86
-
87
- def key_to_consul(key)
88
- key.downcase.gsub(/[^0-9a-z]/i, '_')
89
- end
90
-
91
- def key_to_env(key)
92
- key.upcase.gsub(/[^0-9a-z]/i, '_')
93
- end
94
-
95
- def dereferenced_value(value)
96
- if @opts[:dereference] && value && value[/^consul:\/\//]
97
- reference_path = value.gsub(/^consul:\/\//, '')
98
- dereferenced_value(Diplomat::Kv.get(reference_path))
99
- else
100
- value
101
- end
102
- end
103
-
104
- if config = @opts[:config]
105
- @opts[:config] = YAML.load(config == '-' ? STDIN.read : File.read(config))
106
- end
107
-
108
- if @opts[:init]
109
- raise OptionParser::MissingArgument.new('config') unless @opts[:config]
110
-
111
- services = if service = @opts[:service]
112
- {
113
- service => @opts[:config][service]
114
- }
115
- else
116
- @opts[:config]
117
- end
118
-
119
- services.each_pair do |service, config|
120
- next unless config
121
- next if service[/^\./] # skip hidden keys
122
-
123
- path = "services/env/#{service}"
124
- config.each_pair do |env, item|
125
- key = "#{path}/#{key_to_consul(env)}"
126
- value = if @opts[:upload] && item['file']
127
- File.read(item['file'])
128
- else
129
- item['value'] || item['default'] || item['file']
130
- end
131
-
132
- empty = begin
133
- Diplomat::Kv.get(key)
134
- false
135
- rescue Diplomat::KeyNotFound
136
- true
137
- end
138
-
139
- Diplomat::Kv.put(key, value.to_s.strip) || die("Can't put #{key} to Consul") if empty || @opts[:override]
140
- end
141
- end
142
-
143
- exit 0
144
- end
145
-
146
- if @opts[:show]
147
- config = {}
148
-
149
- path = if service = @opts[:service]
150
- "services/env/#{service}/"
151
- else
152
- 'services/env/'
153
- end
154
-
155
- answer = Diplomat::Kv.get(path, recurse: true, convert_to_hash: true) || die("Can't get #{path} from Consul")
156
- answer['services']['env'].each_pair do |service, env|
157
- cfg = config[service] ||= {}
158
-
159
- env.each_pair do |key, value|
160
- value = dereferenced_value(value)
161
-
162
- cfg[key_to_env(key)] = {
163
- env: key_to_env(key),
164
- value: value
165
- }
166
- end
167
- end
168
-
169
- STDOUT.puts JSON.parse(config.to_json).to_yaml
170
-
171
- exit 0
172
- end
173
-
174
- if put = @opts[:put]
175
- path, *value = put.split(':').map(&:strip)
176
- value = value.join(':')
177
- value = File.read(value) if @opts[:upload] && value && File.exist?(value)
178
-
179
- Diplomat::Kv.put(path, value.to_s.strip) || die("Can't put #{path} to Consul")
180
-
181
- exit 0
182
- end
183
-
184
- if path = @opts[:get]
185
- value = dereferenced_value(Diplomat::Kv.get(path))
186
-
187
- STDOUT.puts value.to_s.strip
188
-
189
- exit 0
190
- end
191
-
192
- if prefix = @opts[:env]
193
- keys = begin
194
- Diplomat::Kv.get(prefix, keys: true)
195
- rescue Diplomat::KeyNotFound => e
196
- []
197
- rescue StandardError
198
- die("Can't get keys at #{prefix} from Consul")
199
- end
200
-
201
- env = keys.reduce({}) do |e, key|
202
- value = dereferenced_value(Diplomat::Kv.get(key))
203
-
204
- e.merge(key_to_env(key.gsub(prefix, '')) => value)
205
- end
206
-
207
- if cmd = @opts[:exec]
208
- env = ENV.to_h.merge(env) unless @opts[:pristine]
209
-
210
- exec(env, cmd, unsetenv_others: true)
211
- else
212
- env.each_pair do |k, v|
213
- STDOUT.puts "#{@opts[:export] ? 'export ' : ''}#{k}=\"#{v}\""
214
- end
215
- end
216
-
217
-
218
- exit 0
219
- end
220
-
221
- STDOUT.puts parser.help
222
- exit 1
data/bin/locker.rb DELETED
@@ -1,71 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'optparse'
4
- require 'diplomat'
5
-
6
- @opts = {
7
- url: 'http://localhost:8500',
8
- timeout: 10,
9
- ttl: 30 * 60
10
- }
11
-
12
- parser = OptionParser.new do |o|
13
- o.banner = 'Usage: locker.rb [options]'
14
-
15
- o.on("--consul url=#{@opts[:url]}", 'Set up a custom Consul URL') do |url|
16
- Diplomat.configure do |config|
17
- config.url = url.strip
18
- end
19
- end
20
-
21
- o.on('--lock resource', 'resource name to lock with Consul') do |resource|
22
- @opts[:lock] = resource.strip
23
- end
24
-
25
- o.on("--ttl seconds=#{@opts[:ttl]}", 'TTL to set when session created') do |seconds|
26
- @opts[:ttl] = Integer(seconds.strip)
27
- end
28
-
29
- o.on('--unlock session', 'session name from previous call lock') do |session|
30
- @opts[:unlock] = session.strip
31
- end
32
-
33
- o.on("--timeout seconds=#{@opts[:timeout]}", 'timeout to wait lock') do |seconds|
34
- @opts[:timeout] = Integer(seconds.strip)
35
- end
36
- end
37
- parser.parse!
38
-
39
-
40
- def lock(session, locker, timeout)
41
- Timeout.timeout(timeout) do
42
- return Diplomat::Lock.wait_to_acquire("resource/#{locker[:resource]}/lock", session, locker.to_json, 10)
43
- end
44
- rescue Timeout::Error => e
45
- false
46
- end
47
-
48
-
49
- if resource = @opts[:lock]
50
- locker = {
51
- Name: "#{resource}_locker_#{rand(999_999)}",
52
- Behavior: 'delete',
53
- TTL: "#{@opts[:ttl]}s",
54
- resource: resource
55
- }
56
- sessionid = Diplomat::Session.create(locker)
57
-
58
- if lock(sessionid, locker, @opts[:timeout])
59
- puts sessionid
60
- exit 0
61
- else
62
- STDERR.puts "Failed to lock resource: #{resource}"
63
- exit 1
64
- end
65
-
66
- elsif session = @opts[:unlock]
67
- Diplomat::Session.destroy(session)
68
- else
69
- STDERR.puts parser.help
70
- exit 1
71
- end
data/bin/merger.rb DELETED
@@ -1,118 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'English'
4
- require 'yaml'
5
-
6
- if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
7
- unless ENV['COMPOSE_FILE']
8
- STDERR.puts 'COMPOSE_FILE environment must point to one or more files'
9
- exit 1
10
- end
11
- end
12
-
13
-
14
- class Hash
15
-
16
- def deep_dup
17
- Marshal.load(Marshal.dump(self))
18
- end
19
-
20
- end
21
-
22
- class Array
23
-
24
- def deep_dup
25
- Marshal.load(Marshal.dump(self))
26
- end
27
-
28
- end
29
-
30
-
31
-
32
- def extend_hash(first, second)
33
- raise ArgumentError.new('First and second args equal nil') if [first, second].all? &:nil?
34
- return second if first.nil?
35
- return first if second.nil?
36
-
37
- first.each_pair do |fk, fv|
38
- next unless second.key?(fk)
39
-
40
- sv = second[fk]
41
- raise "Types of values not match(#{fv.class}, #{sv.class})" if fv.class != sv.class
42
-
43
- # Специальный случай потому что command не мерджится а заменяется
44
- if fk == 'command'
45
- first[fk] = sv
46
- elsif fv.is_a? Hash
47
- extend_hash(fv, sv)
48
- elsif fv.is_a? Array
49
- fv |= sv
50
- first[fk] = fv
51
- else
52
- first[fk] = sv
53
- end
54
- end
55
-
56
- second.each_pair do |sk, sv|
57
- next if first.key?(sk)
58
-
59
- first[sk] = sv
60
- end
61
-
62
- first
63
- end
64
-
65
- def process_compose_hash(yml, dirname, parent = {})
66
- (yml['services'] || {}).each_pair do |name, service|
67
- next unless ext = service['extends']
68
- base = if ext.is_a? String
69
- template = yml['services'][ext]
70
- parent_service = (parent['services'] || {})[ext] || {}
71
- extend_hash(parent_service.deep_dup, template)
72
- elsif file = ext['file']
73
- ENV.each_pair do |k, v|
74
- file.gsub!("$#{k}", v)
75
- file.gsub!("${#{k}}", v)
76
- end
77
-
78
- file_to_load = if File.exist?(dirname + '/' + file)
79
- dirname + '/' + file
80
- else
81
- file
82
- end
83
-
84
- tmp = process_compose_hash(YAML.load(File.read(file_to_load)), File.dirname(file_to_load), service)
85
-
86
- begin
87
- (tmp['services'][ext['service']] || {})
88
- rescue StandardError
89
- {}
90
- end
91
- else
92
- yml['services'][ext['service']]
93
- end.deep_dup
94
-
95
- service.delete 'extends'
96
-
97
- yml['services'][name] = extend_hash(base, service)
98
- end
99
- yml
100
- end
101
-
102
- if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
103
- result = ENV['COMPOSE_FILE'].split(':').reduce({}) do |parent, file|
104
- yml = process_compose_hash(YAML.load(File.read(file)), File.dirname(file), parent)
105
- if yml['version'] && parent['version'] && yml['version'] != parent['version']
106
- raise "version mismatch: #{file}"
107
- end
108
- ret = extend_hash(parent.deep_dup, yml)
109
- ret
110
- end
111
-
112
- if ARGV[0].nil? || ARGV[0].strip == '-'
113
- puts YAML.dump(result)
114
- else
115
- File.write(ARGV[0].strip, YAML.dump(result))
116
- end
117
-
118
- end
data/bin/waiter.rb DELETED
@@ -1,262 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'optparse'
4
- require 'securerandom'
5
- require 'English'
6
- require 'openssl'
7
- require 'tempfile'
8
- require 'json'
9
-
10
- STDOUT.sync = true
11
- STDERR.sync = true
12
-
13
- TIMEOUT = 15
14
- INTERVAL = 3
15
-
16
- @opts = {}
17
-
18
- @opts[:exec] = (begin
19
- ARGV.join(' ').split(' -- ')[1].strip
20
- rescue StandardError
21
- nil
22
- end)
23
- @opts[:timeout] = TIMEOUT
24
- @opts[:interval] = INTERVAL
25
- @opts[:consul_addr] = 'http://localhost:8500'
26
- @opts[:consul_service_count] = 1
27
-
28
-
29
- OptionParser.new do |o|
30
- o.banner = 'Usage: waiter.rb [options] -- exec'
31
-
32
- o.on('--tcp host:port', 'Wait for tcp accepts on host:port') do |addr|
33
- host, port = addr.split(':')
34
- @opts[:tcp] = addr.strip
35
- @opts[:host] = host.strip
36
- @opts[:port] = port.strip
37
- end
38
-
39
- o.on('--db dbname', 'Wait for PG database exists. Using --tcp to conenct PG') do |db|
40
- @opts[:db] = db.strip
41
- end
42
-
43
- o.on('--tb tablename', 'Wait for PG table exists. Using --tcp to conenct PG') do |tb|
44
- @opts[:tb] = tb.strip
45
- end
46
-
47
- o.on('-f', '--file filename', 'Wait for file exists.') do |file|
48
- @opts[:file] = file.strip
49
- end
50
-
51
- o.on("--consul-addr addr=#{@opts[:consul_addr]}", 'HTTP addres to connect to consul') do |addr|
52
- @opts[:consul_addr] = addr.strip
53
- end
54
-
55
- o.on('--consul', 'Wait for local consul agent to be ready') do
56
- @opts[:consul] = true
57
- end
58
-
59
- o.on('--consul-service service', 'Wait for service appear in consul') do |service|
60
- @opts[:consul_service] = service
61
- end
62
-
63
- o.on("--consul-service-count count=#{@opts[:consul_service_count]}", 'Wait for this count of service appear in consul') do |count|
64
- @opts[:consul_service_count] = count.to_i
65
- end
66
-
67
- o.on('--consul-tag tag', 'Filter consul service by tag') do |tag|
68
- @opts[:consul_tag] = tag.to_s
69
- end
70
-
71
- o.on('--user user', 'username') do |user|
72
- @opts[:user] = user.strip
73
- end
74
-
75
- o.on('--pass pass', 'password') do |pass|
76
- @opts[:pass] = pass.strip
77
- end
78
-
79
- o.on('-t', '--timeout secs=15', 'Total timeout') do |timeout|
80
- @opts[:timeout] = timeout.to_i
81
- end
82
-
83
- o.on('-i', '--interval secs=2', 'Interval between attempts') do |interval|
84
- @opts[:interval] = interval.to_i
85
- end
86
-
87
- o.on('-q', '--quiet', 'Do not output any status messages') do
88
- @opts[:quiet] = true
89
- end
90
- end.parse!
91
-
92
- def log(message)
93
- puts message unless @opts[:quiet]
94
- end
95
-
96
- @opts[:timeout] = @opts[:timeout].to_i
97
- @opts[:interval] = @opts[:interval].to_i
98
-
99
- if @opts[:db]
100
- @pg = {}
101
- @pg[:db] = "-d #{@opts[:db]}"
102
- @pg[:user] = "-U #{@opts[:user]}" if @opts[:user]
103
- @pg[:pass] = if @opts[:pass] && !@opts[:pass].empty?
104
- "PGPASSWORD=#{@opts[:pass]}"
105
- else
106
- ''
107
- end
108
-
109
- @pg[:host] = "-h #{@opts[:host]}" if @opts[:host]
110
- @pg[:port] = "-p #{@opts[:port]}" if @opts[:port]
111
-
112
- @pg[:tb] = @opts[:tb] if @opts[:tb]
113
- end
114
-
115
- def wait_for(timeout)
116
- starttime = Time.now
117
- loop do
118
- success = yield
119
-
120
- return success if success
121
-
122
- return false if (Time.now - starttime) > timeout
123
- sleep @opts[:interval]
124
- end
125
-
126
- false
127
- end
128
-
129
- def complete!(success)
130
- if success
131
- if @opts[:exec]
132
- exec @opts[:exec]
133
- else
134
- exit 0
135
- end
136
- end
137
-
138
- STDERR.puts 'Operation timed out'
139
- exit 1
140
- end
141
-
142
- def wait_for_consul
143
- log('Waiting for consul...')
144
- ret = wait_for @opts[:timeout] do
145
- cmd = "consul operator raft list-peers -http-addr=#{@opts[:consul_addr]} > /dev/null 2>&1"
146
- system(cmd)
147
- $?.success?
148
- end
149
-
150
- yield(ret)
151
- end
152
-
153
- def wait_for_consul_service(service)
154
- log("Waiting for consul service #{service}...")
155
- ret = wait_for @opts[:timeout] do
156
- cmd = "curl -s #{@opts[:consul_addr]}/v1/health/service/#{service}?passing"
157
- cmd += "&tag=#{@opts[:consul_tag]}" if @opts[:consul_tag]
158
- result = `#{cmd}`
159
- result = begin
160
- JSON.parse(result)
161
- rescue StandardError
162
- []
163
- end
164
- result.count >= @opts[:consul_service_count]
165
- end
166
-
167
- yield(ret)
168
- end
169
-
170
- def wait_for_tcp
171
- log("Waiting for TCP: #{@opts[:host]}:#{@opts[:port]}...")
172
- ret = wait_for @opts[:timeout] do
173
- cmd = "nc -z #{@opts[:host]} #{@opts[:port]} > /dev/null 2>&1"
174
- system(cmd)
175
- $?.success?
176
- end
177
-
178
- yield(ret)
179
- end
180
-
181
- def wait_for_db
182
- log("Waiting for DB: pg://#{@opts[:user]}:#{@opts[:pass]}@#{@opts[:host]}:#{@opts[:port]}/#{@opts[:db]}...")
183
- ret = wait_for @opts[:timeout] do
184
- cmd = "#{@pg[:pass]} psql -lqt #{@pg[:user]} #{@pg[:host]} #{@pg[:port]} #{@pg[:db]} 2>/dev/null | cut -d \\| -f 1 | grep -qw #{@opts[:db]} > /dev/null 2>&1"
185
- system(cmd)
186
- $?.success?
187
- end
188
-
189
- yield(ret)
190
- end
191
-
192
- def wait_for_tb
193
- log("Waiting for TABLE: pg://#{@opts[:user]}:#{@opts[:pass]}@#{@opts[:host]}:#{@opts[:port]}/#{@opts[:db]}##{@opts[:tb]}...")
194
- ret = wait_for @opts[:timeout] do
195
- cmd = "echo \"\\dt\" | psql -qt #{@pg[:user]} #{@pg[:pass]} #{@pg[:host]} #{@pg[:port]} #{@pg[:db]} 2>/dev/null | cut -d \\| -f 2 | grep -qw #{@pg[:tb]} > /dev/null 2>&1"
196
- system(cmd)
197
- $?.success?
198
- end
199
-
200
- yield(ret)
201
- end
202
-
203
- def wait_for_file(file = @opts[:file], timeout = @opts[:timeout])
204
- log("Waiting for FILE: #{file}")
205
- ret = wait_for timeout do
206
- File.exist? file
207
- end
208
- yield(ret)
209
- end
210
-
211
- if @opts[:tb]
212
- wait_for_tcp do |success|
213
- if success
214
- wait_for_db do |success|
215
- if success
216
- wait_for_tb do |success|
217
- complete!(success)
218
- end
219
- end
220
- end
221
- end
222
-
223
- complete!(false)
224
- end
225
- end
226
-
227
- if @opts[:db]
228
- wait_for_tcp do |success|
229
- if success
230
- wait_for_db do |success|
231
- complete!(success)
232
- end
233
- end
234
-
235
- complete!(false)
236
- end
237
- end
238
-
239
-
240
- if @opts[:consul]
241
- wait_for_consul do |success|
242
- complete!(success)
243
- end
244
- end
245
-
246
- if @opts[:consul_service]
247
- wait_for_consul_service(@opts[:consul_service]) do |success|
248
- complete!(success)
249
- end
250
- end
251
-
252
- if @opts[:tcp]
253
- wait_for_tcp do |success|
254
- complete!(success)
255
- end
256
- end
257
-
258
- if @opts[:file]
259
- wait_for_file(@opts[:file], @opts[:timeout]) do |success|
260
- complete!(success)
261
- end
262
- end