dry-stack 0.0.86 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0030379ed099994251cd619cde54371206b3cc12028e7fb2a17b6d56296f0e37'
4
- data.tar.gz: 6aa330ee8d7456a70eba51f961a4084c5a466b3ff0e3dfd819dc58ffa24452d4
3
+ metadata.gz: 571e75a339b0f3ad9ffb0c4d5816e4de8451b2ec3f7b3bf7a31030eef31dd176
4
+ data.tar.gz: 13b9b28f6d78a75f5d08e718743b89c4f466d2134e0ca36a89e268f557b46d90
5
5
  SHA512:
6
- metadata.gz: 57a60be8b72a438f818904b412970e49f26fbe049cb5a926ca30d3d8414b7e7cdba1aded7c7b7d00efbcb6a2f873807a0f11a4c8b1bf339c9ca1400802e09315
7
- data.tar.gz: 6a7359355a1068dda4ef13873742ae3603925c868cf55b1a30ec98a798a2e11850dfad3846bfbd4938d9419d0aec734d90181b8f240573dcb44d5f4e2ff57c18
6
+ metadata.gz: 32a9c2c618aeacc2efdd3892bcb1b708e3a071b6cc3a975b9e81464dbe703e374205397de4d158f6218c11898db7401d0f21f45a397878ce0153c9a9446a86d3
7
+ data.tar.gz: 5f7450cef3b2d997d1a53b0b906384282529747ccd373b9c693fbc40fae5d1e33562657833cacd7e8f0c0e60d824ae552807a3b3227333d6e4e166dd808d56d7
@@ -1,14 +1,13 @@
1
1
  require_relative 'command_line'
2
2
 
3
3
  Dry::CommandLine::COMMANDS[:to_compose] = Class.new do
4
- def run(stack, params, args, extra)
5
- raise "none or one deploy name may be specified: #{args}" unless args.empty? || args.size == 1
4
+ def run(stack, params, _args, _extra)
6
5
  _params = stack.options.merge params
7
- stack.name = _params[:name] if _params[:name]
8
- yaml = stack.to_compose(_params, args[0]&.to_sym ).lines[1..].join
6
+ yaml = stack.to_compose(_params ).lines[1..].join
7
+ $stdout.puts yaml
9
8
 
10
9
  # substitute ENV variables
11
- _params[:'no-env'] ? $stdout.puts(yaml) : system("echo \"#{yaml.gsub("`", '\\\`')}\"")
10
+ # _params[:'no-env'] ? $stdout.puts(yaml) : system("echo \"#{yaml.gsub("`", '\\\`')}\"")
12
11
  end
13
12
 
14
13
  def help = ['Print stack in docker compose format',
@@ -37,7 +37,9 @@ module Dry
37
37
  raise 'Invalid .env file'
38
38
  end
39
39
 
40
- def safe_eval(str) = Dry::Stack() { eval str, self.binding }
40
+ def safe_eval(drs, params)
41
+ Dry::Stack(params[:name], params[:configuration]) { eval drs, self.binding }
42
+ end
41
43
 
42
44
  def run(args)
43
45
  params = {}
@@ -68,9 +70,12 @@ module Dry
68
70
  o.on('', '--name STACK_NAME', 'Define stack name')
69
71
  o.on('', '--ingress', 'Generate ingress labels') { true }
70
72
  o.on('', '--traefik', 'Generate traefik labels') { true }
71
- o.on('', '--traefik_tls', 'Generate traefik tls labels') { true }
72
- o.on('', '--host_sed /from/to/', 'Sed ingress host /\\*/dev.*/')
73
- o.on('-n', '--no-env', 'Do not process env variables') { true }
73
+ o.on('', '--traefik-tls', 'Generate traefik tls labels') { true }
74
+ o.on('', '--host-sed /from/to/', 'Sed ingress host /\\*/dev.*/')
75
+ o.on('-n', '--no-env', 'Deprecated') { $stderr.puts 'warning: deprecated option: -n' } # TODO: remove
76
+ o.on('-c', '--configuration name', 'Configuration name')
77
+ COMMANDS.values.select{_1.options(o) if _1.respond_to? :options }
78
+
74
79
  o.on('-h', '--help') { puts o; exit }
75
80
  o.parse! args, into: params
76
81
 
@@ -82,7 +87,8 @@ module Dry
82
87
  stack_text = File.read(params[:stack]) if params[:stack]
83
88
  stack_text ||= STDIN.read unless $stdin.tty?
84
89
 
85
- safe_eval stack_text # isolate context
90
+
91
+ safe_eval stack_text, params # isolate context
86
92
 
87
93
  Stack.last_stack.name = params[:name] if params[:name]
88
94
  COMMANDS[command.to_sym].run Stack.last_stack, params, args, extra
@@ -1,41 +1,35 @@
1
1
  require_relative 'command_line'
2
2
 
3
3
  Dry::CommandLine::COMMANDS[:swarm_deploy] = Class.new do
4
+ def options(parser)
5
+ parser.on('-x', '--context-endpoint host', 'Docker context host. Created if not exists')
6
+ end
7
+
4
8
  def run(stack, params, args, extra)
5
- unless args.empty?
6
- c_name = args[0].to_sym
7
- raise "deploy config not found: #{args[0]}" unless stack.swarm_deploy.key? args[0].to_sym
8
- context = stack.swarm_deploy[args[0].to_sym]
9
- end
10
9
  _params = stack.options.merge params
11
10
  stack.name = _params[:name] if _params[:name]
12
11
 
13
- if context
14
- name = context[:context_name]&.to_sym || args[0].to_sym
15
- host = context[:context_host]
12
+ if params[:'context-endpoint']
13
+ name = params[:'context-endpoint'].gsub( /[\/.:@]/,'_').gsub( '__','_')
14
+ name = "dry-#{name}".to_sym
15
+ endpoint = params[:'context-endpoint']
16
16
  contexts = {}
17
- exec_o_lines "docker context ls --format json" do |line|
17
+ exec_o_lines 'docker context ls --format json' do |line|
18
18
  ctx = JSON.parse line, symbolize_names: true
19
19
  contexts[ctx[:Name].to_sym] = ctx # {"Current":false,"Description":"","DockerEndpoint":"ssh://root@x.x.x.x","Error":"","Name":"prod-swarm"}
20
20
  end
21
21
 
22
- if contexts[name] && contexts[name][:DockerEndpoint] != host
23
- raise "context '#{name}' has different host value: #{contexts[name][:DockerEndpoint]} != #{host}"
22
+ if contexts[name] && contexts[name][:DockerEndpoint] != endpoint
23
+ raise "context '#{name}' has different host value: #{contexts[name][:DockerEndpoint]} != #{endpoint}"
24
24
  end
25
25
 
26
- exec_i "docker context create #{name} --docker host=#{host}" unless contexts[name]
26
+ exec_i "docker context create #{name} --docker host=#{endpoint}" unless contexts[name]
27
27
 
28
28
  ENV['DOCKER_CONTEXT'] = name.to_s
29
- stack.name = context[:stack_name] || stack.name
30
- context[:environment].each do |k,v|
31
- ENV[k.to_s] = v.to_s
32
- end
33
29
  end
34
30
 
35
31
  # substitute ENV variables
36
- yaml = stack.to_compose(_params, c_name).lines[1..].join
37
- yaml = _params[:'no-env'] ? yaml : `echo \"#{yaml.gsub("`", '\\\`')}\"`
38
- system " echo \"#{yaml.gsub("`", '\\\`')}\"" if _params[:v]
32
+ yaml = stack.to_compose(_params).lines[1..].join
39
33
  # system " echo \"#{yaml.gsub("`", '\\\`')}\" | docker stack deploy -c - #{stack.name} --prune --resolve-image changed"
40
34
 
41
35
  # --prune --resolve-image changed
@@ -48,7 +42,7 @@ Dry::CommandLine::COMMANDS[:swarm_deploy] = Class.new do
48
42
  end
49
43
 
50
44
  def help = ['Call docker stack deploy & add config readme w/ description',
51
- '[... swarm_deploy sd_name -- --prune --resolve-image changed]']
45
+ '[... swarm_deploy -- --prune --resolve-image changed]']
52
46
 
53
47
  end.new
54
48
 
@@ -39,9 +39,10 @@ module Dry
39
39
  end
40
40
  end
41
41
 
42
- def Stack(name = nil, &)
42
+ def Stack(name = nil, configuration = nil, &)
43
43
  Stack.last_stack = Stack.new name
44
44
  Stack.last_stack.instance_exec(&) if block_given?
45
+ Stack.last_stack.apply_configuration configuration if configuration
45
46
  end
46
47
 
47
48
  class ServiceFunction
@@ -61,13 +62,8 @@ module Dry
61
62
  def ingress(ing) = ((@service[:ingress] ||=[]) << ing).flatten!
62
63
  end
63
64
 
64
- class SwarmFunction
65
- def initialize(swarm, &); @swarm = swarm; instance_exec(&) end
66
- def env(variables)= @swarm[:environment].merge! variables
67
- def options(variables)= @swarm[:options].merge! variables
68
- def context_host(host)= @swarm[:context_host] = host
69
- def context_name(name)= @swarm[:context_name] = name
70
- def stack_name(name)= @swarm[:stack_name] = name
65
+ class ConfigurationFunction
66
+ def initialize(configuration, &); @configuration = configuration; instance_exec(&) end
71
67
  end
72
68
 
73
69
  class Stack
@@ -75,12 +71,13 @@ module Dry
75
71
  class << self
76
72
  attr_accessor :last_stack
77
73
  end
78
- attr_accessor :name, :options, :description, :swarm_deploy
74
+ attr_accessor :name, :options, :description, :configuration
79
75
 
80
- def Stack(name = nil, &)
81
- Stack.last_stack = Stack.new name
82
- Stack.last_stack.instance_exec(&) if block_given?
83
- end
76
+ def Stack(...) = Dry::Stack(...)
77
+
78
+ # def self.new(*args, &block)
79
+ # super
80
+ # end
84
81
 
85
82
  def initialize(name)
86
83
  @name = name || 'stack'
@@ -97,7 +94,7 @@ module Dry
97
94
  @labels = {}
98
95
  @configs = {}
99
96
  @logging = {}
100
- @swarm_deploy = {}
97
+ @configurations = {}
101
98
  end
102
99
 
103
100
  def expand_hash(hash)
@@ -123,12 +120,13 @@ module Dry
123
120
  end
124
121
  end
125
122
 
126
- def to_compose(opts = @options, deploy_name = nil )
127
- if deploy_name
128
- raise "Deploy not found: #{deploy_name}" unless @swarm_deploy[deploy_name]
123
+ def apply_configuration(configuration)
124
+ raise "Configuration not found: #{configuration}" unless @configurations[configuration.to_sym]
125
+ @configurations[configuration.to_sym][:block_function].call @configurations[configuration.to_sym]
126
+ end
129
127
 
130
- opts.merge! @swarm_deploy[deploy_name][:options]
131
- end
128
+ def to_compose(opts = @options)
129
+ @name = @options[:name] || @name
132
130
 
133
131
  compose = {
134
132
  # name: @name.to_s, # https://docs.docker.com/compose/compose-file/#name-top-level-element
@@ -146,6 +144,8 @@ module Dry
146
144
 
147
145
  compose[:services].each do |name, service|
148
146
 
147
+ service[:image].gsub!(/:latest$/, '') # let docker swarm to create tag: :latest@sha265:0000...
148
+
149
149
  ingress = [@ingress[name], service[:ingress] || [] ].flatten.compact
150
150
 
151
151
  service[:deploy] ||= {}
@@ -268,15 +268,18 @@ module Dry
268
268
  end
269
269
 
270
270
  compose[:configs].update(compose[:configs]) do |name, config|
271
+ # total config name must be max 64 characters length. MD5 - 32 characters
272
+ short_name = name[0..30]
273
+
271
274
  if config[:file_content]
272
275
  md5 = Digest::MD5.hexdigest config[:file_content]
273
- fname = "./#{@name}.config.#{name}.#{md5}" # use MD5, when rn in parallel may have different content
276
+ fname = "./#{@name}.config.#{name}.#{md5}" # use MD5, when run in parallel may have different content
274
277
  File.write fname, config[:file_content]
275
- {name: "#{name}-#{md5}", file: fname}.merge config.except(:file_content)
278
+ {name: "#{short_name}-#{md5}", file: fname}.merge config.except(:file_content)
276
279
  elsif config[:file]
277
280
  body = File.read config[:file] rescue ''
278
281
  md5 = Digest::MD5.hexdigest body
279
- {name: "#{name}-#{md5}", file: fname}.merge config
282
+ {name: "#{short_name}-#{md5}", file: fname}.merge config
280
283
  else
281
284
  config
282
285
  end
@@ -318,8 +321,8 @@ module Dry
318
321
  end
319
322
 
320
323
  def Options(opts)
321
- warn 'WARN: Options command is used for testing purpose.\
322
- Not recommended in real life configurations.' unless $0 =~ /rspec/
324
+ # warn 'WARN: Options command is used for testing purpose.\
325
+ # Not recommended in real life configurations.' unless $0 =~ /rspec/
323
326
  @options.merge! opts
324
327
  end
325
328
 
@@ -328,7 +331,9 @@ module Dry
328
331
  end
329
332
 
330
333
  def Ingress(services)
331
- @ingress.merge! services
334
+ services.each do |name, ing|
335
+ @ingress[name] = ((@ingress[name] || [] ) | [ing]).flatten
336
+ end
332
337
  end
333
338
 
334
339
  def Config(name, opts)
@@ -377,12 +382,12 @@ module Dry
377
382
  yield if block_given?
378
383
  end
379
384
 
380
- def SwarmDeploy(name, opts = {}, &)
381
- opts[:environment] = opts.delete(:env) if opts.key? :env
382
-
383
- swarm = @swarm_deploy[name.to_sym] ||= { environment: {}, options: {} }
384
- swarm.merge! opts
385
- SwarmFunction.new(swarm, &) if block_given?
385
+ def Configuration(name, opts = {}, &)
386
+ configuration = @configurations[name.to_sym] ||= { }
387
+ configuration.merge! opts
388
+ configuration.merge! block_function: ->(*args){
389
+ self.instance_exec(&) if block_given?
390
+ }
386
391
  end
387
392
 
388
393
  end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  class Stack
3
- VERSION = '0.0.86'
3
+ VERSION = '0.1.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-stack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.86
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artyom B
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-10 00:00:00.000000000 Z
11
+ date: 2024-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -102,9 +102,6 @@ extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
104
  - bin/dry-stack
105
- - bin/stack.drs
106
- - bin/stack1.drs
107
- - bin/stack2.drs
108
105
  - lib/dry-stack.rb
109
106
  - lib/dry-stack/apache_specific_md5.rb
110
107
  - lib/dry-stack/command_compose.rb
data/bin/stack.drs DELETED
@@ -1,33 +0,0 @@
1
- Description <<~DSC
2
-
3
- DSC
4
-
5
- Options name: 'stack_name', traefik: true
6
- SwarmDeploy :sky_gates do
7
- context_host 'ssh://root@10.0.0.1'
8
- stack_name 'remote_stack'
9
- env REGISTRY_HOST: '10.100.0.2:5000'
10
- end
11
- Ingress admin: [
12
- {host: 'backend.*'},
13
- {host: 'admin.*', path: '/api', port: 4000}
14
- ]
15
-
16
- Deploy [:admin, :operator], labels: [
17
- 'traefik.http.middlewares.%{service-name}_auth.basicauth.users=admin:$$apr1$$i7hdbc9g$$Rkocxo9snhmuESvUg0TTv/',
18
- "traefik.http.routers.%{service-name}.middlewares=%{service-name}_auth"
19
- ]
20
-
21
- PublishPorts admin: 4000, operator: 4001, navigator: 4002, reports: 7000 # mode: ingress, protocol: tcp
22
-
23
- Service :admin, image: 'frontend', env: {APP: 'admin'}, ports: 5000
24
- Service :operator, image: 'frontend', env: {APP: 'operator'}, ports: 5000
25
- Service :navigator, image: 'frontend', env: {APP: 'navigator'}, ports: 5000
26
-
27
- Service :backend, image: 'backend', ports: 3000 do
28
- env APP_PORT: 3000, NODE_ENV: 'development', SKIP_GZ: true, DB_URL: '$DB_URL'
29
- end
30
-
31
- Service :reports, image: 'reports:0.1', env: {DB_URL: '$DB_URL'}, ports: 7000
32
-
33
- Network :default, attachable: true
data/bin/stack1.drs DELETED
@@ -1 +0,0 @@
1
- Service :reports, image: 'reports', ports: 7000, env: {DB_URL: '$DB_URL'}
data/bin/stack2.drs DELETED
@@ -1,8 +0,0 @@
1
- PublishPorts reports: 7000
2
- Ingress reports: {host: 'reports.*', protocol: :http, port: 7000}
3
- Deploy admin: { replica: 2, 'resources.limits': { cpus: 4, memory: '500M' } }
4
-
5
- Service :admin, image: '$REGISTRY_HOST/frontend', env: { APP: 'admin' }, ports: 5000
6
- Service :reports, image: '$REGISTRY_HOST/reports', env: { DB_URL: '$DB_URL' }
7
-
8
- Network :default, attachable: true