simple_infrastructure 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 +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +301 -0
- data/bin/simple_infrastructure +6 -0
- data/lib/generators/simple_infrastructure/change_generator.rb +16 -0
- data/lib/generators/simple_infrastructure/templates/change.rb.tt +21 -0
- data/lib/simple_infrastructure/change.rb +187 -0
- data/lib/simple_infrastructure/cli.rb +306 -0
- data/lib/simple_infrastructure/configuration.rb +36 -0
- data/lib/simple_infrastructure/dry_run_connection.rb +34 -0
- data/lib/simple_infrastructure/dsl.rb +53 -0
- data/lib/simple_infrastructure/file_operations.rb +48 -0
- data/lib/simple_infrastructure/inventory.rb +44 -0
- data/lib/simple_infrastructure/railtie.rb +22 -0
- data/lib/simple_infrastructure/runner.rb +162 -0
- data/lib/simple_infrastructure/server.rb +39 -0
- data/lib/simple_infrastructure/ssh_connection.rb +91 -0
- data/lib/simple_infrastructure/toml_operations.rb +154 -0
- data/lib/simple_infrastructure/version.rb +5 -0
- data/lib/simple_infrastructure/yaml_operations.rb +79 -0
- data/lib/simple_infrastructure.rb +57 -0
- data/lib/tasks/simple_infrastructure.rake +79 -0
- metadata +96 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleInfrastructure
|
|
4
|
+
class Server
|
|
5
|
+
attr_reader :hostname, :env, :user, :attributes
|
|
6
|
+
|
|
7
|
+
def initialize(hostname:, env:, user: "root", **attributes)
|
|
8
|
+
@hostname = hostname
|
|
9
|
+
@env = env.to_sym
|
|
10
|
+
@user = user
|
|
11
|
+
@attributes = attributes
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def matches?(target)
|
|
15
|
+
return false if target[:env] && env != target[:env].to_sym
|
|
16
|
+
|
|
17
|
+
if target[:hostname]
|
|
18
|
+
case target[:hostname]
|
|
19
|
+
when Regexp
|
|
20
|
+
return false unless hostname.match?(target[:hostname])
|
|
21
|
+
when String
|
|
22
|
+
return false unless hostname == target[:hostname]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_h
|
|
30
|
+
{
|
|
31
|
+
"hostname" => hostname
|
|
32
|
+
}.merge(attributes.transform_keys(&:to_s))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
"#{user}@#{hostname} (#{env})"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/ssh"
|
|
4
|
+
|
|
5
|
+
module SimpleInfrastructure
|
|
6
|
+
class SshConnection
|
|
7
|
+
attr_reader :server
|
|
8
|
+
|
|
9
|
+
def initialize(server)
|
|
10
|
+
@server = server
|
|
11
|
+
@ssh = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def connect
|
|
15
|
+
@ssh = Net::SSH.start(
|
|
16
|
+
server.hostname,
|
|
17
|
+
server.user,
|
|
18
|
+
forward_agent: true,
|
|
19
|
+
verify_host_key: :accept_new
|
|
20
|
+
)
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def disconnect
|
|
25
|
+
@ssh&.close
|
|
26
|
+
@ssh = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def exec(command)
|
|
30
|
+
stdout = ""
|
|
31
|
+
stderr = ""
|
|
32
|
+
exit_code = nil
|
|
33
|
+
|
|
34
|
+
@ssh.open_channel do |channel|
|
|
35
|
+
channel.exec(command) do |ch, success|
|
|
36
|
+
raise "Failed to execute command" unless success
|
|
37
|
+
|
|
38
|
+
ch.on_data { |_, data| stdout += data }
|
|
39
|
+
ch.on_extended_data { |_, _, data| stderr += data }
|
|
40
|
+
ch.on_request("exit-status") { |_, data| exit_code = data.read_long }
|
|
41
|
+
end
|
|
42
|
+
end.wait
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
stdout: stdout,
|
|
46
|
+
stderr: stderr,
|
|
47
|
+
exit_code: exit_code,
|
|
48
|
+
success: exit_code.zero?
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def read_file(path, sudo: false)
|
|
53
|
+
cmd = "cat #{shell_escape(path)} 2>/dev/null || echo ''"
|
|
54
|
+
cmd = "sudo #{cmd}" if sudo
|
|
55
|
+
result = exec(cmd)
|
|
56
|
+
result[:stdout]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def write_file(path, content, sudo: false)
|
|
60
|
+
# Use heredoc to safely write content
|
|
61
|
+
delimiter = "INFRASTRUCTURE_EOF_#{rand(100_000)}"
|
|
62
|
+
cmd = "cat > #{shell_escape(path)} << '#{delimiter}'\n#{content}\n#{delimiter}"
|
|
63
|
+
if sudo
|
|
64
|
+
# For sudo, write to temp file then move
|
|
65
|
+
tmp = "/tmp/infrastructure_#{rand(100_000)}"
|
|
66
|
+
exec("cat > #{tmp} << '#{delimiter}'\n#{content}\n#{delimiter}")
|
|
67
|
+
exec("sudo mv #{tmp} #{shell_escape(path)}")
|
|
68
|
+
else
|
|
69
|
+
exec(cmd)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def file_exists?(path, sudo: false)
|
|
74
|
+
cmd = "test -f #{shell_escape(path)}"
|
|
75
|
+
cmd = "sudo #{cmd}" if sudo
|
|
76
|
+
result = exec(cmd)
|
|
77
|
+
result[:success]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def shell_escape(str)
|
|
83
|
+
# Handle ~ expansion: don't quote the ~/ prefix
|
|
84
|
+
if str.start_with?("~/")
|
|
85
|
+
"~/'#{str[2..].gsub("'", "'\\''")}'"
|
|
86
|
+
else
|
|
87
|
+
"'#{str.gsub("'", "'\\''")}'"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleInfrastructure
|
|
4
|
+
class TomlOperations
|
|
5
|
+
def initialize(connection, path, sudo: false, dry_run: false)
|
|
6
|
+
@connection = connection
|
|
7
|
+
@path = path
|
|
8
|
+
@sudo = sudo
|
|
9
|
+
@dry_run = dry_run
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def apply(operations)
|
|
13
|
+
content = @connection.read_file(@path, sudo: @sudo)
|
|
14
|
+
data = content.empty? ? {} : parse_toml(content)
|
|
15
|
+
modified = false
|
|
16
|
+
|
|
17
|
+
operations.each do |op, *args|
|
|
18
|
+
case op
|
|
19
|
+
when :set
|
|
20
|
+
key, value = args
|
|
21
|
+
current = deep_get(data, key)
|
|
22
|
+
if current != value
|
|
23
|
+
SimpleInfrastructure.logger.debug " #{key}: #{current.inspect} -> #{value.inspect}"
|
|
24
|
+
deep_set(data, key, value)
|
|
25
|
+
modified = true
|
|
26
|
+
end
|
|
27
|
+
when :remove
|
|
28
|
+
key = args[0]
|
|
29
|
+
if deep_has?(data, key)
|
|
30
|
+
SimpleInfrastructure.logger.debug " remove: #{key}"
|
|
31
|
+
deep_remove(data, key)
|
|
32
|
+
modified = true
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if modified
|
|
38
|
+
new_content = dump_toml(data)
|
|
39
|
+
unless @dry_run
|
|
40
|
+
@connection.write_file(@path, new_content, sudo: @sudo)
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
SimpleInfrastructure.logger.debug " (no changes needed)"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def parse_toml(content)
|
|
52
|
+
data = {}
|
|
53
|
+
current_section = data
|
|
54
|
+
|
|
55
|
+
content.each_line do |line|
|
|
56
|
+
line = line.strip
|
|
57
|
+
next if line.empty? || line.start_with?("#")
|
|
58
|
+
|
|
59
|
+
if line =~ /^\[([^\]]+)\]$/
|
|
60
|
+
# Section header
|
|
61
|
+
current_section = deep_ensure(data, ::Regexp.last_match(1))
|
|
62
|
+
elsif line =~ /^([^=]+)=(.*)$/
|
|
63
|
+
key = ::Regexp.last_match(1).strip
|
|
64
|
+
value = parse_toml_value(::Regexp.last_match(2).strip)
|
|
65
|
+
current_section[key] = value
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
data
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def parse_toml_value(str)
|
|
73
|
+
case str
|
|
74
|
+
when /^"(.*)"$/, /^'(.*)'$/ then ::Regexp.last_match(1)
|
|
75
|
+
when /^true$/i then true
|
|
76
|
+
when /^false$/i then false
|
|
77
|
+
when /^-?\d+$/ then str.to_i
|
|
78
|
+
when /^-?\d+\.\d+$/ then str.to_f
|
|
79
|
+
when /^\[(.*)\]$/
|
|
80
|
+
::Regexp.last_match(1).split(",").map { |v| parse_toml_value(v.strip) }
|
|
81
|
+
else
|
|
82
|
+
str
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def dump_toml(data, prefix = nil)
|
|
87
|
+
simple_keys = []
|
|
88
|
+
table_keys = []
|
|
89
|
+
|
|
90
|
+
data.each do |key, value|
|
|
91
|
+
if value.is_a?(Hash)
|
|
92
|
+
table_keys << key
|
|
93
|
+
else
|
|
94
|
+
simple_keys << key
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Output simple key=value pairs first
|
|
99
|
+
lines = simple_keys.map do |key|
|
|
100
|
+
"#{key} = #{toml_value(data[key])}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Then tables
|
|
104
|
+
table_keys.each do |key|
|
|
105
|
+
full_key = prefix ? "#{prefix}.#{key}" : key
|
|
106
|
+
lines << ""
|
|
107
|
+
lines << "[#{full_key}]"
|
|
108
|
+
lines << dump_toml(data[key], full_key)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
lines.join("\n")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def toml_value(value)
|
|
115
|
+
case value
|
|
116
|
+
when String then "\"#{value}\""
|
|
117
|
+
when true, false, Integer, Float then value.to_s
|
|
118
|
+
when Array then "[#{value.map { |v| toml_value(v) }.join(', ')}]"
|
|
119
|
+
else value.to_s.inspect
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def deep_get(hash, key_path)
|
|
124
|
+
keys = key_path.split(".")
|
|
125
|
+
keys.reduce(hash) { |h, k| h.is_a?(Hash) ? h[k] : nil }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def deep_set(hash, key_path, value)
|
|
129
|
+
keys = key_path.split(".")
|
|
130
|
+
last_key = keys.pop
|
|
131
|
+
target = keys.reduce(hash) { |h, k| h[k] ||= {} }
|
|
132
|
+
target[last_key] = value
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def deep_has?(hash, key_path)
|
|
136
|
+
keys = key_path.split(".")
|
|
137
|
+
last_key = keys.pop
|
|
138
|
+
target = keys.reduce(hash) { |h, k| h.is_a?(Hash) ? h[k] : nil }
|
|
139
|
+
target.is_a?(Hash) && target.key?(last_key)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def deep_remove(hash, key_path)
|
|
143
|
+
keys = key_path.split(".")
|
|
144
|
+
last_key = keys.pop
|
|
145
|
+
target = keys.reduce(hash) { |h, k| h.is_a?(Hash) ? h[k] : nil }
|
|
146
|
+
target.delete(last_key) if target.is_a?(Hash)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def deep_ensure(hash, key_path)
|
|
150
|
+
keys = key_path.split(".")
|
|
151
|
+
keys.reduce(hash) { |h, k| h[k] ||= {} }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module SimpleInfrastructure
|
|
6
|
+
class YamlOperations
|
|
7
|
+
def initialize(connection, path, sudo: false, dry_run: false)
|
|
8
|
+
@connection = connection
|
|
9
|
+
@path = path
|
|
10
|
+
@sudo = sudo
|
|
11
|
+
@dry_run = dry_run
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def apply(operations)
|
|
15
|
+
content = @connection.read_file(@path, sudo: @sudo)
|
|
16
|
+
data = content.empty? ? {} : YAML.safe_load(content) || {}
|
|
17
|
+
modified = false
|
|
18
|
+
|
|
19
|
+
operations.each do |op, *args|
|
|
20
|
+
case op
|
|
21
|
+
when :set
|
|
22
|
+
key, value = args
|
|
23
|
+
current = deep_get(data, key)
|
|
24
|
+
if current != value
|
|
25
|
+
SimpleInfrastructure.logger.debug " #{key}: #{current.inspect} -> #{value.inspect}"
|
|
26
|
+
deep_set(data, key, value)
|
|
27
|
+
modified = true
|
|
28
|
+
end
|
|
29
|
+
when :remove
|
|
30
|
+
key = args[0]
|
|
31
|
+
if deep_has?(data, key)
|
|
32
|
+
SimpleInfrastructure.logger.debug " remove: #{key}"
|
|
33
|
+
deep_remove(data, key)
|
|
34
|
+
modified = true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if modified
|
|
40
|
+
new_content = YAML.dump(data)
|
|
41
|
+
unless @dry_run
|
|
42
|
+
@connection.write_file(@path, new_content, sudo: @sudo)
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
SimpleInfrastructure.logger.debug " (no changes needed)"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def deep_get(hash, key_path)
|
|
54
|
+
keys = key_path.split(".")
|
|
55
|
+
keys.reduce(hash) { |h, k| h.is_a?(Hash) ? h[k] : nil }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def deep_set(hash, key_path, value)
|
|
59
|
+
keys = key_path.split(".")
|
|
60
|
+
last_key = keys.pop
|
|
61
|
+
target = keys.reduce(hash) { |h, k| h[k] ||= {} }
|
|
62
|
+
target[last_key] = value
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def deep_has?(hash, key_path)
|
|
66
|
+
keys = key_path.split(".")
|
|
67
|
+
last_key = keys.pop
|
|
68
|
+
target = keys.reduce(hash) { |h, k| h.is_a?(Hash) ? h[k] : nil }
|
|
69
|
+
target.is_a?(Hash) && target.key?(last_key)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def deep_remove(hash, key_path)
|
|
73
|
+
keys = key_path.split(".")
|
|
74
|
+
last_key = keys.pop
|
|
75
|
+
target = keys.reduce(hash) { |h, k| h.is_a?(Hash) ? h[k] : nil }
|
|
76
|
+
target.delete(last_key) if target.is_a?(Hash)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "erb"
|
|
5
|
+
require "net/ssh"
|
|
6
|
+
require "fileutils"
|
|
7
|
+
|
|
8
|
+
require_relative "simple_infrastructure/version"
|
|
9
|
+
require_relative "simple_infrastructure/configuration"
|
|
10
|
+
|
|
11
|
+
module SimpleInfrastructure
|
|
12
|
+
autoload :Change, "simple_infrastructure/change"
|
|
13
|
+
autoload :RunStep, "simple_infrastructure/change"
|
|
14
|
+
autoload :FileStep, "simple_infrastructure/change"
|
|
15
|
+
autoload :OnChangeContext, "simple_infrastructure/change"
|
|
16
|
+
autoload :YamlStep, "simple_infrastructure/change"
|
|
17
|
+
autoload :TomlStep, "simple_infrastructure/change"
|
|
18
|
+
autoload :UploadStep, "simple_infrastructure/change"
|
|
19
|
+
autoload :Server, "simple_infrastructure/server"
|
|
20
|
+
autoload :Inventory, "simple_infrastructure/inventory"
|
|
21
|
+
autoload :Runner, "simple_infrastructure/runner"
|
|
22
|
+
autoload :Dsl, "simple_infrastructure/dsl"
|
|
23
|
+
autoload :FileOperations, "simple_infrastructure/file_operations"
|
|
24
|
+
autoload :YamlOperations, "simple_infrastructure/yaml_operations"
|
|
25
|
+
autoload :TomlOperations, "simple_infrastructure/toml_operations"
|
|
26
|
+
autoload :SshConnection, "simple_infrastructure/ssh_connection"
|
|
27
|
+
autoload :DryRunConnection, "simple_infrastructure/dry_run_connection"
|
|
28
|
+
autoload :Cli, "simple_infrastructure/cli"
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
def configuration
|
|
32
|
+
@configuration ||= Configuration.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def configure
|
|
36
|
+
yield(configuration)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def logger
|
|
40
|
+
configuration.logger
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def root
|
|
44
|
+
configuration.config_dir
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def project_root
|
|
48
|
+
configuration.project_root
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def reset_configuration!
|
|
52
|
+
@configuration = Configuration.new
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
require "simple_infrastructure/railtie" if defined?(Rails::Railtie)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :infrastructure do
|
|
4
|
+
desc "Show status of all servers"
|
|
5
|
+
task status: :environment do
|
|
6
|
+
require "simple_infrastructure"
|
|
7
|
+
SimpleInfrastructure::Runner.new.status
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
desc "Run pending changes on environment (e.g., rake infrastructure:run[production])"
|
|
11
|
+
task :run, [:target] => :environment do |_t, args|
|
|
12
|
+
require "simple_infrastructure"
|
|
13
|
+
target = args[:target] || ENV["TARGET"]
|
|
14
|
+
abort "Usage: rake infrastructure:run[production] or TARGET=production rake infrastructure:run" unless target
|
|
15
|
+
|
|
16
|
+
runner = SimpleInfrastructure::Runner.new
|
|
17
|
+
success = if target.include?(".")
|
|
18
|
+
runner.run_for_hostname(target)
|
|
19
|
+
else
|
|
20
|
+
runner.run_for_env(target)
|
|
21
|
+
end
|
|
22
|
+
exit(1) unless success
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "Dry run pending changes (e.g., rake infrastructure:dry_run[production])"
|
|
26
|
+
task :dry_run, [:target] => :environment do |_t, args|
|
|
27
|
+
require "simple_infrastructure"
|
|
28
|
+
target = args[:target] || ENV["TARGET"]
|
|
29
|
+
abort "Usage: rake infrastructure:dry_run[production]" unless target
|
|
30
|
+
|
|
31
|
+
puts "=== DRY RUN MODE ===\n\n"
|
|
32
|
+
runner = SimpleInfrastructure::Runner.new(dry_run: true)
|
|
33
|
+
if target.include?(".")
|
|
34
|
+
runner.run_for_hostname(target)
|
|
35
|
+
else
|
|
36
|
+
runner.run_for_env(target)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc "Generate a new change (e.g., rake infrastructure:generate[setup_redis])"
|
|
41
|
+
task :generate, [:name] => :environment do |_t, args|
|
|
42
|
+
require "simple_infrastructure"
|
|
43
|
+
name = args[:name]
|
|
44
|
+
abort "Usage: rake infrastructure:generate[change_name]" unless name
|
|
45
|
+
|
|
46
|
+
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
|
47
|
+
filename = "#{timestamp}_#{name}.rb"
|
|
48
|
+
path = File.join(SimpleInfrastructure.configuration.changes_dir, filename)
|
|
49
|
+
|
|
50
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
51
|
+
|
|
52
|
+
template = <<~RUBY
|
|
53
|
+
target env: :production
|
|
54
|
+
|
|
55
|
+
# run "command", sudo: true
|
|
56
|
+
|
|
57
|
+
# file "/path/to/file", sudo: true do
|
|
58
|
+
# contains "line that must exist"
|
|
59
|
+
# remove "line that must not exist"
|
|
60
|
+
# on_change { run "systemctl restart service", sudo: true }
|
|
61
|
+
# end
|
|
62
|
+
|
|
63
|
+
# yaml "/path/to/config.yml", sudo: true do
|
|
64
|
+
# set "key.path", "value"
|
|
65
|
+
# remove "old.key"
|
|
66
|
+
# end
|
|
67
|
+
|
|
68
|
+
# toml "/path/to/config.toml", sudo: true do
|
|
69
|
+
# set "key.path", "value"
|
|
70
|
+
# remove "old.key"
|
|
71
|
+
# end
|
|
72
|
+
|
|
73
|
+
# upload "config/backup/script.sh", "/root/bin/script.sh", sudo: true, mode: "700"
|
|
74
|
+
RUBY
|
|
75
|
+
|
|
76
|
+
File.write(path, template)
|
|
77
|
+
puts "Created: #{path}"
|
|
78
|
+
end
|
|
79
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: simple_infrastructure
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- FounderCatalyst
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: logger
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: net-ssh
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '7.0'
|
|
40
|
+
description: Simple Infrastructure provides a change-based approach to server configuration,
|
|
41
|
+
similar to how Rails database migrations work. Each change is tracked as a versioned
|
|
42
|
+
file, ensuring idempotency, auditability, and consistency across servers.
|
|
43
|
+
email:
|
|
44
|
+
- dev@foundercatalyst.com
|
|
45
|
+
executables:
|
|
46
|
+
- simple_infrastructure
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- CHANGELOG.md
|
|
51
|
+
- LICENSE.txt
|
|
52
|
+
- README.md
|
|
53
|
+
- bin/simple_infrastructure
|
|
54
|
+
- lib/generators/simple_infrastructure/change_generator.rb
|
|
55
|
+
- lib/generators/simple_infrastructure/templates/change.rb.tt
|
|
56
|
+
- lib/simple_infrastructure.rb
|
|
57
|
+
- lib/simple_infrastructure/change.rb
|
|
58
|
+
- lib/simple_infrastructure/cli.rb
|
|
59
|
+
- lib/simple_infrastructure/configuration.rb
|
|
60
|
+
- lib/simple_infrastructure/dry_run_connection.rb
|
|
61
|
+
- lib/simple_infrastructure/dsl.rb
|
|
62
|
+
- lib/simple_infrastructure/file_operations.rb
|
|
63
|
+
- lib/simple_infrastructure/inventory.rb
|
|
64
|
+
- lib/simple_infrastructure/railtie.rb
|
|
65
|
+
- lib/simple_infrastructure/runner.rb
|
|
66
|
+
- lib/simple_infrastructure/server.rb
|
|
67
|
+
- lib/simple_infrastructure/ssh_connection.rb
|
|
68
|
+
- lib/simple_infrastructure/toml_operations.rb
|
|
69
|
+
- lib/simple_infrastructure/version.rb
|
|
70
|
+
- lib/simple_infrastructure/yaml_operations.rb
|
|
71
|
+
- lib/tasks/simple_infrastructure.rake
|
|
72
|
+
homepage: https://github.com/foundercatalyst/simple_infrastructure
|
|
73
|
+
licenses:
|
|
74
|
+
- MIT
|
|
75
|
+
metadata:
|
|
76
|
+
homepage_uri: https://github.com/foundercatalyst/simple_infrastructure
|
|
77
|
+
source_code_uri: https://github.com/foundercatalyst/simple_infrastructure
|
|
78
|
+
changelog_uri: https://github.com/foundercatalyst/simple_infrastructure/blob/main/CHANGELOG.md
|
|
79
|
+
rdoc_options: []
|
|
80
|
+
require_paths:
|
|
81
|
+
- lib
|
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '3.1'
|
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
|
+
requirements:
|
|
89
|
+
- - ">="
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: '0'
|
|
92
|
+
requirements: []
|
|
93
|
+
rubygems_version: 4.0.3
|
|
94
|
+
specification_version: 4
|
|
95
|
+
summary: A migration-like DSL for server provisioning via SSH
|
|
96
|
+
test_files: []
|