caco 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +10 -0
  4. data/CODE_OF_CONDUCT.md +74 -0
  5. data/Gemfile +12 -0
  6. data/Gemfile.lock +227 -0
  7. data/Guardfile +42 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +97 -0
  10. data/Rakefile +10 -0
  11. data/bin/_guard-core +29 -0
  12. data/bin/bundle +114 -0
  13. data/bin/byebug +29 -0
  14. data/bin/caco +29 -0
  15. data/bin/coderay +29 -0
  16. data/bin/console +20 -0
  17. data/bin/eyaml +29 -0
  18. data/bin/guard +29 -0
  19. data/bin/listen +29 -0
  20. data/bin/pry +29 -0
  21. data/bin/rake +29 -0
  22. data/bin/safe_yaml +29 -0
  23. data/bin/setup +8 -0
  24. data/bin/thor +29 -0
  25. data/bin/tilt +29 -0
  26. data/caco.gemspec +43 -0
  27. data/exe/caco +27 -0
  28. data/lib/caco.rb +115 -0
  29. data/lib/caco/barman.rb +10 -0
  30. data/lib/caco/barman/cell/global.rb +4 -0
  31. data/lib/caco/barman/cell/node.rb +15 -0
  32. data/lib/caco/barman/install.rb +25 -0
  33. data/lib/caco/barman/view/global.erb +7 -0
  34. data/lib/caco/barman/view/node.erb +8 -0
  35. data/lib/caco/cell.rb +8 -0
  36. data/lib/caco/config.rb +23 -0
  37. data/lib/caco/debian.rb +28 -0
  38. data/lib/caco/debian/add_user.rb +18 -0
  39. data/lib/caco/debian/apt_key_install.rb +19 -0
  40. data/lib/caco/debian/apt_repo_add.rb +17 -0
  41. data/lib/caco/debian/apt_sources_list.rb +15 -0
  42. data/lib/caco/debian/apt_update.rb +33 -0
  43. data/lib/caco/debian/cell/service.rb +19 -0
  44. data/lib/caco/debian/cell/sources_list.rb +7 -0
  45. data/lib/caco/debian/package_install.rb +21 -0
  46. data/lib/caco/debian/package_installed.rb +17 -0
  47. data/lib/caco/debian/service_enable.rb +26 -0
  48. data/lib/caco/debian/service_install.rb +31 -0
  49. data/lib/caco/debian/user_home.rb +17 -0
  50. data/lib/caco/debian/view/service.erb +18 -0
  51. data/lib/caco/debian/view/sources_list.erb +10 -0
  52. data/lib/caco/downloader.rb +41 -0
  53. data/lib/caco/executer.rb +33 -0
  54. data/lib/caco/facter.rb +41 -0
  55. data/lib/caco/file_link.rb +36 -0
  56. data/lib/caco/file_reader.rb +24 -0
  57. data/lib/caco/file_writer.rb +57 -0
  58. data/lib/caco/finder.rb +13 -0
  59. data/lib/caco/grafana.rb +6 -0
  60. data/lib/caco/grafana/install.rb +26 -0
  61. data/lib/caco/haproxy.rb +12 -0
  62. data/lib/caco/haproxy/cell/conf_postgres.rb +4 -0
  63. data/lib/caco/haproxy/cell/conf_stats.rb +4 -0
  64. data/lib/caco/haproxy/conf_get.rb +15 -0
  65. data/lib/caco/haproxy/conf_set.rb +52 -0
  66. data/lib/caco/haproxy/install.rb +9 -0
  67. data/lib/caco/haproxy/view/conf_postgres.erb +25 -0
  68. data/lib/caco/haproxy/view/conf_stats.erb +6 -0
  69. data/lib/caco/macro.rb +2 -0
  70. data/lib/caco/postgres.rb +44 -0
  71. data/lib/caco/postgres/build_augeas.rb +20 -0
  72. data/lib/caco/postgres/conf_get.rb +37 -0
  73. data/lib/caco/postgres/conf_set.rb +54 -0
  74. data/lib/caco/postgres/database_create.rb +28 -0
  75. data/lib/caco/postgres/extension_create.rb +28 -0
  76. data/lib/caco/postgres/hba_set.rb +65 -0
  77. data/lib/caco/postgres/install.rb +34 -0
  78. data/lib/caco/postgres/shell.rb +13 -0
  79. data/lib/caco/postgres/sql.rb +17 -0
  80. data/lib/caco/postgres/user_change_password.rb +13 -0
  81. data/lib/caco/postgres/user_create.rb +33 -0
  82. data/lib/caco/prometheus.rb +15 -0
  83. data/lib/caco/prometheus/adapter_install_pg.rb +107 -0
  84. data/lib/caco/prometheus/adapter_install_postgresql.rb +47 -0
  85. data/lib/caco/prometheus/cell/alertmanager_conf.rb +4 -0
  86. data/lib/caco/prometheus/cell/alerts.rb +4 -0
  87. data/lib/caco/prometheus/cell/conf.rb +7 -0
  88. data/lib/caco/prometheus/exporter_install.rb +35 -0
  89. data/lib/caco/prometheus/install.rb +50 -0
  90. data/lib/caco/prometheus/install_alert_manager.rb +62 -0
  91. data/lib/caco/prometheus/view/alertmanager_conf.erb +13 -0
  92. data/lib/caco/prometheus/view/alerts.erb +18 -0
  93. data/lib/caco/prometheus/view/conf.erb +34 -0
  94. data/lib/caco/rbenv.rb +8 -0
  95. data/lib/caco/rbenv/cell/profile.rb +4 -0
  96. data/lib/caco/rbenv/install.rb +56 -0
  97. data/lib/caco/rbenv/install_version.rb +17 -0
  98. data/lib/caco/rbenv/view/profile.erb +3 -0
  99. data/lib/caco/repmgr.rb +15 -0
  100. data/lib/caco/repmgr/cell/conf.rb +43 -0
  101. data/lib/caco/repmgr/conf.rb +25 -0
  102. data/lib/caco/repmgr/install.rb +25 -0
  103. data/lib/caco/repmgr/node_register_primary.rb +34 -0
  104. data/lib/caco/repmgr/node_register_standby.rb +25 -0
  105. data/lib/caco/repmgr/node_registered.rb +15 -0
  106. data/lib/caco/repmgr/node_role.rb +18 -0
  107. data/lib/caco/repmgr/view/conf.erb +27 -0
  108. data/lib/caco/settings_loader.rb +67 -0
  109. data/lib/caco/settings_loader_monkeypatch.rb +28 -0
  110. data/lib/caco/ssh.rb +6 -0
  111. data/lib/caco/ssh/authorized_keys_add.rb +82 -0
  112. data/lib/caco/sudo.rb +6 -0
  113. data/lib/caco/sudo/sudoers_add.rb +15 -0
  114. data/lib/caco/timescale.rb +6 -0
  115. data/lib/caco/timescale/install.rb +25 -0
  116. data/lib/caco/unpacker.rb +76 -0
  117. data/lib/caco/version.rb +3 -0
  118. metadata +398 -0
@@ -0,0 +1,13 @@
1
+ module Caco
2
+ class Finder < Trailblazer::Operation
3
+ step Subprocess(Caco::Executer),
4
+ input: [:command],
5
+ output: { exit_code: :command_exit_code, output: :command_output },
6
+ id: "execute_command"
7
+
8
+ step ->(ctx, command_output:, regexp:, **) {
9
+ command_output.freeze.match?(regexp)
10
+ },
11
+ id: :match_regexp
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module Caco
2
+ module Grafana
3
+ end
4
+ end
5
+
6
+ require 'caco/grafana/install'
@@ -0,0 +1,26 @@
1
+ module Caco::Grafana
2
+ class Install < Trailblazer::Operation
3
+ class Repo < Trailblazer::Operation
4
+ step Subprocess(Caco::Debian::AptKeyInstall),
5
+ input: ->(_ctx, **) {{
6
+ url: 'https://packages.grafana.com/gpg.key',
7
+ fingerprint: '4E40 DDF6 D76E 284A 4A67 80E4 8C8C 34C5 2409 8CB6'
8
+ }}
9
+
10
+ step Subprocess(Caco::Debian::AptRepoAdd),
11
+ input: ->(_ctx, **) {{
12
+ name: 'grafana',
13
+ url: 'https://packages.grafana.com/oss/deb',
14
+ release: "stable",
15
+ component: 'main'
16
+ }}
17
+ end
18
+
19
+ step Subprocess(Repo)
20
+ step Subprocess(Caco::Debian::AptUpdate)
21
+ step Subprocess(Caco::Debian::PackageInstall),
22
+ input: ->(_ctx, **) {{
23
+ package: 'grafana'
24
+ }}
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ module Caco
2
+ module Haproxy
3
+ end
4
+ end
5
+
6
+ require 'caco/haproxy/conf_get'
7
+ require 'caco/haproxy/conf_set'
8
+ require 'caco/haproxy/install'
9
+
10
+ # Templates
11
+ require 'caco/haproxy/cell/conf_postgres'
12
+ require 'caco/haproxy/cell/conf_stats'
@@ -0,0 +1,4 @@
1
+ module Caco::Haproxy::Cell
2
+ class ConfPostgres < Trailblazer::Cell
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Caco::Haproxy::Cell
2
+ class ConfStats < Trailblazer::Cell
3
+ end
4
+ end
@@ -0,0 +1,15 @@
1
+ class Caco::Haproxy::ConfGet < Trailblazer::Operation
2
+ step Subprocess(Caco::FileReader),
3
+ input: ->(_ctx, **) {{
4
+ path: "/etc/default/haproxy",
5
+ }},
6
+ output: [:output]
7
+
8
+ step ->(ctx, name:, output:, **) {
9
+ match = output.match(/^#{name}=\"(.*)\"/)
10
+ return false unless match
11
+
12
+ ctx[:value] = match[1]
13
+ },
14
+ id: :find_value
15
+ end
@@ -0,0 +1,52 @@
1
+ class Caco::Haproxy::ConfSet < Trailblazer::Operation
2
+ step Subprocess(Caco::FileReader),
3
+ input: ->(_ctx, **) {{
4
+ path: "/etc/default/haproxy",
5
+ }},
6
+ output: [:output]
7
+
8
+ step Subprocess(Caco::Haproxy::ConfGet),
9
+ input: ->(_ctx, name:, **) {{
10
+ name: name,
11
+ }},
12
+ output: {value: :existing_value}
13
+
14
+ step :change_value
15
+
16
+ fail :create_value, Output(:success) => Track(:success)
17
+
18
+ step :check_values_are_the_same,
19
+ Output(:success) => End(:success),
20
+ Output(:failure) => Track(:success)
21
+
22
+ step Subprocess(Caco::FileWriter),
23
+ input: ->(_ctx, new_config_content:, **) {{
24
+ path: "/etc/default/haproxy",
25
+ content: new_config_content
26
+ }}
27
+
28
+ def change_value(ctx, output:, name:, value:, **)
29
+ ctx[:created] = false
30
+ ctx[:changed] = true
31
+ ctx[:new_config_content] = output.gsub!(/^#{name}=\"(.*)\"/, "#{name}=\"#{value}\"")
32
+ true
33
+ end
34
+
35
+ def create_value(ctx, output:, name:, value:, **)
36
+ ctx[:created] = true
37
+ ctx[:changed] = true
38
+ output << "#{name}=\"#{value}\"\n"
39
+ ctx[:new_config_content] = output
40
+ true
41
+ end
42
+
43
+ def check_values_are_the_same(ctx, value:, existing_value:, **)
44
+ if value == existing_value
45
+ ctx[:changed] = nil
46
+ ctx[:created] = nil
47
+ true
48
+ else
49
+ false
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,9 @@
1
+ module Caco::Haproxy
2
+ class Install < Trailblazer::Operation
3
+ step Subprocess(Caco::Debian::AptUpdate)
4
+ step Subprocess(Caco::Debian::PackageInstall),
5
+ input: ->(_ctx, **) {{
6
+ package: 'haproxy'
7
+ }}
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ listen haproxy_10.0.0.30_3307_rw
2
+ bind *:3307
3
+ mode tcp
4
+ timeout client 10800s
5
+ timeout server 10800s
6
+ tcp-check expect string primary\ is\ running
7
+ balance leastconn
8
+ option tcp-check
9
+ option allbackups
10
+ default-server port 9300 inter 2s downinter 5s rise 3 fall 2 slowstart 60s maxconn 64 maxqueue 128 weight 100
11
+ server 10.0.0.21 10.0.0.21:5432 check
12
+ server 10.0.0.22 10.0.0.22:5432 check
13
+
14
+ listen haproxy_10.0.0.30_3308_ro
15
+ bind *:3308
16
+ mode tcp
17
+ timeout client 10800s
18
+ timeout server 10800s
19
+ tcp-check expect string is\ running.
20
+ balance leastconn
21
+ option tcp-check
22
+ option allbackups
23
+ default-server port 9300 inter 2s downinter 5s rise 3 fall 2 slowstart 60s maxconn 64 maxqueue 128 weight 100
24
+ server 10.0.0.21 10.0.0.21:5432 check
25
+ server 10.0.0.22 10.0.0.22:5432 check
@@ -0,0 +1,6 @@
1
+ frontend stats
2
+ bind *:8404
3
+ stats enable
4
+ stats uri /stats
5
+ stats refresh 10s
6
+ stats admin if LOCALHOST
@@ -0,0 +1,2 @@
1
+ module Caco::Macro
2
+ end
@@ -0,0 +1,44 @@
1
+ module Caco
2
+ module Postgres
3
+ module ClassMethods
4
+ def add_shared_library(lib)
5
+ @libraries ||= []
6
+ @libraries << lib
7
+ true
8
+ end
9
+
10
+ def shared_libraries
11
+ @libraries ||= []
12
+ @libraries.join(", ")
13
+ end
14
+
15
+ def clear_shared_library
16
+ @libraries = []
17
+ end
18
+
19
+ def should_restart!
20
+ @should_restart = true
21
+ end
22
+
23
+ def should_restart?
24
+ @should_restart || false
25
+ end
26
+ end
27
+
28
+ extend ClassMethods
29
+ end
30
+ end
31
+
32
+ require 'caco/postgres/build_augeas'
33
+ require 'caco/postgres/conf_get'
34
+ require 'caco/postgres/conf_set'
35
+ require 'caco/postgres/hba_set'
36
+ require 'caco/postgres/install'
37
+ require 'caco/postgres/shell'
38
+ require 'caco/postgres/sql'
39
+
40
+ # depends on sql
41
+ require 'caco/postgres/database_create'
42
+ require 'caco/postgres/extension_create'
43
+ require 'caco/postgres/user_change_password'
44
+ require 'caco/postgres/user_create'
@@ -0,0 +1,20 @@
1
+ module Caco::Postgres
2
+ def self.BuildAugeas
3
+ task = ->((ctx, flow_options), _) do
4
+ augeas_path = ctx[:augeas_path] ? ctx[:augeas_path] : "/"
5
+
6
+ ctx[:aug] = aug = Augeas::open(augeas_path, nil, Augeas::NO_LOAD)
7
+ aug.clear_transforms
8
+ aug.transform(:lens => "Postgresql.lns", :incl => "/postgresql.conf")
9
+ aug.load
10
+
11
+ return Trailblazer::Activity::Right, [ctx, flow_options]
12
+ end
13
+
14
+ # new API
15
+ {
16
+ task: task,
17
+ id: "build_augeas"
18
+ }
19
+ end
20
+ end
@@ -0,0 +1,37 @@
1
+ module Caco::Postgres
2
+ class ConfGet < Trailblazer::Operation
3
+ ProcessSingleValue = Class.new(Trailblazer::Activity::Signal)
4
+ ProcessMultipleValue = Class.new(Trailblazer::Activity::Signal)
5
+
6
+ step Caco::Postgres::BuildAugeas()
7
+
8
+ step :define_what_process,
9
+ Output(ProcessSingleValue, :single_value) => Id(:process_single_value),
10
+ Output(ProcessMultipleValue, :multiple_values) => Id(:process_multiple_values),
11
+ Output(Trailblazer::Activity::Left, :failure) => End(:failure)
12
+
13
+ step :process_single_value, magnetic_to: nil
14
+ step :process_multiple_values, magnetic_to: nil
15
+
16
+ def define_what_process(ctx, name: nil, names: nil, **)
17
+ if name and name.is_a?(String)
18
+ return ProcessSingleValue
19
+ elsif names and names.is_a?(Array)
20
+ return ProcessMultipleValue
21
+ else
22
+ return false
23
+ end
24
+ end
25
+
26
+ def process_single_value(ctx, name:, aug:, **)
27
+ ctx[:value] = aug.get("/files/postgresql.conf/#{name}")
28
+ end
29
+
30
+ def process_multiple_values(ctx, names:, aug:, **)
31
+ ctx[:values] = {}
32
+ names.each do |name|
33
+ ctx[:values][name.to_s] = aug.get("/files/postgresql.conf/#{name}")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,54 @@
1
+ module Caco::Postgres
2
+ class ConfSet < Trailblazer::Operation
3
+ ProcessSingleValue = Class.new(Trailblazer::Activity::Signal)
4
+ ProcessMultipleValue = Class.new(Trailblazer::Activity::Signal)
5
+
6
+ step Caco::Postgres::BuildAugeas()
7
+
8
+ step :define_what_process,
9
+ Output(ProcessSingleValue, :single_value) => Id(:process_single_value),
10
+ Output(ProcessMultipleValue, :multiple_values) => Id(:process_multiple_values),
11
+ Output(Trailblazer::Activity::Left, :failure) => End(:failure)
12
+ step :process_single_value, magnetic_to: nil
13
+ step :process_multiple_values, magnetic_to: nil
14
+
15
+ def define_what_process(ctx, name: nil, names: nil, value: nil, values: nil, **)
16
+ if name && value
17
+ return ProcessSingleValue
18
+ elsif values and values.is_a?(Hash)
19
+ return ProcessMultipleValue
20
+ else
21
+ return false
22
+ end
23
+ end
24
+
25
+ def process_single_value(ctx, name:, value:, aug:, **)
26
+ ctx[:existing_value] = aug.get("/files/postgresql.conf/#{name}")
27
+ ctx[:created] = !ctx[:existing_value]
28
+ if ctx[:existing_value] == value
29
+ return true
30
+ else
31
+ ctx[:changed] = true
32
+ ctx[:value] = aug.set("/files/postgresql.conf/#{name}", value)
33
+ aug.save!
34
+ end
35
+ true
36
+ end
37
+
38
+ def process_multiple_values(ctx, values:, aug:, **)
39
+ ctx[:values] = {}
40
+ values.each_pair do |name, value|
41
+ ctx[:existing_value] = aug.get("/files/postgresql.conf/#{name}")
42
+ ctx[:created] = true unless ctx[:existing_value]
43
+ if ctx[:existing_value] == value
44
+ next
45
+ else
46
+ ctx[:changed] = true
47
+ ctx[:values][name.to_s] = aug.set("/files/postgresql.conf/#{name}", value)
48
+ end
49
+ end
50
+ aug.save! if ctx[:changed]
51
+ true
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,28 @@
1
+ module Caco::Postgres
2
+ class DatabaseCreate < Trailblazer::Operation
3
+
4
+ step Subprocess(Caco::Postgres::Sql),
5
+ input: ->(_ctx, database:, **) {{
6
+ sql: "select datname from pg_database where datname='#{database}';",
7
+ }},
8
+ id: :sql_find_database
9
+
10
+ step ->(_ctx, output:, database:, **) {
11
+ output.match?(/^\s#{database}$/)
12
+ },
13
+ Output(:success) => End(:success),
14
+ Output(:failure) => Track(:success),
15
+ id: :verify_database_exists
16
+
17
+ step Subprocess(Caco::Executer),
18
+ input: ->(ctx, database:, **) {{
19
+ command: "createdb -e #{ctx[:additional_args]} #{database}",
20
+ }},
21
+ id: :create_database
22
+
23
+ step ->(ctx, **) {
24
+ ctx[:created] = ctx[:changed] = true
25
+ },
26
+ id: :mark_created
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ module Caco::Postgres
2
+ class ExtensionCreate < Trailblazer::Operation
3
+
4
+ step Subprocess(Caco::Postgres::Sql),
5
+ input: ->(_ctx, extension:, **) {{
6
+ sql: "select extname from pg_extension where extname='#{extension}';",
7
+ }},
8
+ id: :sql_find_extension
9
+
10
+ step ->(_ctx, output:, extension:, **) {
11
+ output.match?(/^\s#{extension}$/)
12
+ },
13
+ Output(:success) => End(:success),
14
+ Output(:failure) => Track(:success),
15
+ id: :verify_extension_exists
16
+
17
+ step Subprocess(Class.new(Caco::Postgres::Sql)),
18
+ input: ->(_ctx, extension:, **) {{
19
+ sql: "create extension #{extension};",
20
+ }},
21
+ id: :create_extension
22
+
23
+ step ->(ctx, **) {
24
+ ctx[:created] = ctx[:changed] = true
25
+ },
26
+ id: :mark_created
27
+ end
28
+ end
@@ -0,0 +1,65 @@
1
+ class Caco::Postgres::HbaSet < Trailblazer::Operation
2
+ class InvalidType < StandardError; end
3
+ class InvalidMethod < StandardError; end
4
+ class MissingNetworkValue < StandardError; end
5
+ ValidTypes = %w( local host hostssl hostnossl )
6
+ ValidMethods = %w( trust reject md5 password scram-sha-256 gss sspi ident peer pam ldap radius or cert )
7
+
8
+ TypeLocal = Class.new(Trailblazer::Activity::Signal)
9
+ TypeHost = Class.new(Trailblazer::Activity::Signal)
10
+
11
+ step :check_type_is_valid
12
+ step :check_method_is_valid
13
+ step :check_value_exist,
14
+ Output(:success) => End(:success),
15
+ Output(:failure) => Track(:success)
16
+ step :check_if_network_value_is_needed
17
+ step :append_value
18
+ step ->(ctx, **) {
19
+ ctx[:created] = ctx[:changed] = true
20
+ },
21
+ id: :mark_as_changed
22
+
23
+ def check_type_is_valid(ctx, type:, **)
24
+ raise InvalidType.new("`#{type}' is not a valid type") unless ValidTypes.include?(type)
25
+ true
26
+ end
27
+
28
+ def check_method_is_valid(ctx, method:, **)
29
+ raise InvalidMethod.new("`#{method}' is not a valid method") unless ValidMethods.include?(method)
30
+ true
31
+ end
32
+
33
+ def check_value_exist(ctx, input:, type:, database:, user:, method:, **)
34
+ return input.match?(/^#{type}\s+#{database}\s+#{user}\s+#{method}$/) if type == 'local'
35
+ input.match?(/^#{type}\s+#{database}\s+#{user}\s+#{ctx[:network]}\s+#{method}$/)
36
+ end
37
+
38
+ def check_if_network_value_is_needed(ctx, type:, **)
39
+ return true if type == 'local'
40
+ return true if ctx[:network]
41
+ raise MissingNetworkValue.new("You need to enter a value for network when #{type} is specified")
42
+ end
43
+
44
+ def append_value(ctx, input:, type:, database:, user:, method:, **)
45
+ after_type_size = 8 - type.size > 0 ? (8 - type.size) : 1
46
+ after_type_spaces = " " * after_type_size
47
+
48
+ after_db_size = 16 - database.size > 0 ? (16 - database.size) : 1
49
+ after_db_spaces = " " * after_db_size
50
+
51
+ after_user_size = 16 - user.size > 0 ? (16 - user.size) : 1
52
+ after_user_spaces = " " * after_user_size
53
+
54
+ network_size = ctx[:network].size rescue 0
55
+ after_network_size = 24 - network_size > 0 ? 24 - network_size : 1
56
+ after_network_spaces = " " * after_network_size
57
+
58
+ if type == 'local'
59
+ input << "#{type}#{after_type_spaces}#{database}#{after_db_spaces}#{user}#{after_user_spaces}#{after_network_spaces}#{method}\n"
60
+ else
61
+ input << "#{type}#{after_type_spaces}#{database}#{after_db_spaces}#{user}#{after_user_spaces}#{ctx[:network]}#{after_network_spaces}#{method}\n"
62
+ end
63
+ ctx[:content] = input
64
+ end
65
+ end