mrsk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +179 -52
- data/lib/mrsk/cli/accessory.rb +85 -0
- data/lib/mrsk/cli/app.rb +57 -31
- data/lib/mrsk/cli/base.rb +24 -3
- data/lib/mrsk/cli/build.rb +6 -6
- data/lib/mrsk/cli/main.rb +12 -1
- data/lib/mrsk/cli/prune.rb +2 -2
- data/lib/mrsk/cli/registry.rb +6 -2
- data/lib/mrsk/cli/server.rb +1 -1
- data/lib/mrsk/cli/traefik.rb +43 -10
- data/lib/mrsk/cli.rb +3 -3
- data/lib/mrsk/commander.rb +45 -6
- data/lib/mrsk/commands/accessory.rb +61 -0
- data/lib/mrsk/commands/app.rb +48 -8
- data/lib/mrsk/commands/base.rb +13 -9
- data/lib/mrsk/commands/builder/base.rb +26 -0
- data/lib/mrsk/commands/builder/multiarch/remote.rb +9 -6
- data/lib/mrsk/commands/builder/multiarch.rb +11 -8
- data/lib/mrsk/commands/builder/native/remote.rb +71 -0
- data/lib/mrsk/commands/builder/native.rb +3 -7
- data/lib/mrsk/commands/builder.rb +11 -7
- data/lib/mrsk/commands/traefik.rb +13 -3
- data/lib/mrsk/configuration/accessory.rb +60 -0
- data/lib/mrsk/configuration/role.rb +46 -14
- data/lib/mrsk/configuration.rb +83 -42
- data/lib/mrsk/sshkit_with_ext.rb +12 -0
- data/lib/mrsk/utils.rb +29 -0
- data/lib/mrsk/version.rb +1 -1
- metadata +9 -2
@@ -2,22 +2,21 @@ require "mrsk/commands/base"
|
|
2
2
|
|
3
3
|
class Mrsk::Commands::Builder < Mrsk::Commands::Base
|
4
4
|
delegate :create, :remove, :push, :pull, :info, to: :target
|
5
|
-
delegate :native?, :multiarch?, :remote?, to: :name
|
6
5
|
|
7
6
|
def name
|
8
|
-
target.class.to_s.
|
7
|
+
target.class.to_s.remove("Mrsk::Commands::Builder::").underscore
|
9
8
|
end
|
10
9
|
|
11
10
|
def target
|
12
11
|
case
|
13
|
-
when config.builder.
|
14
|
-
multiarch
|
15
|
-
when config.builder["multiarch"] == false
|
12
|
+
when config.builder && config.builder["multiarch"] == false
|
16
13
|
native
|
17
|
-
when config.builder["local"] && config.builder["
|
14
|
+
when config.builder && config.builder["local"] && config.builder["remote"]
|
18
15
|
multiarch_remote
|
16
|
+
when config.builder && config.builder["remote"]
|
17
|
+
native_remote
|
19
18
|
else
|
20
|
-
|
19
|
+
multiarch
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
@@ -25,6 +24,10 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
|
|
25
24
|
@native ||= Mrsk::Commands::Builder::Native.new(config)
|
26
25
|
end
|
27
26
|
|
27
|
+
def native_remote
|
28
|
+
@native ||= Mrsk::Commands::Builder::Native::Remote.new(config)
|
29
|
+
end
|
30
|
+
|
28
31
|
def multiarch
|
29
32
|
@multiarch ||= Mrsk::Commands::Builder::Multiarch.new(config)
|
30
33
|
end
|
@@ -35,5 +38,6 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
|
|
35
38
|
end
|
36
39
|
|
37
40
|
require "mrsk/commands/builder/native"
|
41
|
+
require "mrsk/commands/builder/native/remote"
|
38
42
|
require "mrsk/commands/builder/multiarch"
|
39
43
|
require "mrsk/commands/builder/multiarch/remote"
|
@@ -8,7 +8,8 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
8
8
|
"-p 80:80",
|
9
9
|
"-v /var/run/docker.sock:/var/run/docker.sock",
|
10
10
|
"traefik",
|
11
|
-
"--providers.docker"
|
11
|
+
"--providers.docker",
|
12
|
+
"--log.level=DEBUG"
|
12
13
|
end
|
13
14
|
|
14
15
|
def start
|
@@ -23,8 +24,17 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
23
24
|
docker :ps, "--filter", "name=traefik"
|
24
25
|
end
|
25
26
|
|
26
|
-
def logs
|
27
|
-
|
27
|
+
def logs(since: nil, lines: nil, grep: nil)
|
28
|
+
pipe \
|
29
|
+
docker(:logs, "traefik", (" --since #{since}" if since), (" -n #{lines}" if lines), "-t", "2>&1"),
|
30
|
+
("grep '#{grep}'" if grep)
|
31
|
+
end
|
32
|
+
|
33
|
+
def follow_logs(host:, grep: nil)
|
34
|
+
run_over_ssh pipe(
|
35
|
+
docker(:logs, "traefik", "-t", "-n", "10", "-f", "2>&1"),
|
36
|
+
("grep '#{grep}'" if grep)
|
37
|
+
).join(" "), host: host
|
28
38
|
end
|
29
39
|
|
30
40
|
def remove_container
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Mrsk::Configuration::Assessory
|
2
|
+
delegate :argumentize, :argumentize_env_with_secrets, to: Mrsk::Utils
|
3
|
+
|
4
|
+
attr_accessor :name, :specifics
|
5
|
+
|
6
|
+
def initialize(name, config:)
|
7
|
+
@name, @config, @specifics = name.inquiry, config, config.raw_config["accessories"][name]
|
8
|
+
end
|
9
|
+
|
10
|
+
def service_name
|
11
|
+
"#{config.service}-#{name}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def image
|
15
|
+
specifics["image"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def host
|
19
|
+
specifics["host"] || raise(ArgumentError, "Missing host for accessory")
|
20
|
+
end
|
21
|
+
|
22
|
+
def port
|
23
|
+
if specifics["port"].to_s.include?(":")
|
24
|
+
specifics["port"]
|
25
|
+
else
|
26
|
+
"#{specifics["port"]}:#{specifics["port"]}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def labels
|
31
|
+
default_labels.merge(specifics["labels"] || {})
|
32
|
+
end
|
33
|
+
|
34
|
+
def label_args
|
35
|
+
argumentize "--label", labels
|
36
|
+
end
|
37
|
+
|
38
|
+
def env
|
39
|
+
specifics["env"] || {}
|
40
|
+
end
|
41
|
+
|
42
|
+
def env_args
|
43
|
+
argumentize_env_with_secrets env
|
44
|
+
end
|
45
|
+
|
46
|
+
def volumes
|
47
|
+
specifics["volumes"] || []
|
48
|
+
end
|
49
|
+
|
50
|
+
def volume_args
|
51
|
+
argumentize "--volume", volumes
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
attr_accessor :config
|
56
|
+
|
57
|
+
def default_labels
|
58
|
+
{ "service" => service_name }
|
59
|
+
end
|
60
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class Mrsk::Configuration::Role
|
2
|
-
delegate :argumentize, to: Mrsk::
|
2
|
+
delegate :argumentize, :argumentize_env_with_secrets, to: Mrsk::Utils
|
3
3
|
|
4
4
|
attr_accessor :name
|
5
5
|
|
@@ -12,21 +12,33 @@ class Mrsk::Configuration::Role
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def labels
|
15
|
-
|
16
|
-
default_labels.merge(traefik_labels).merge(custom_labels)
|
17
|
-
else
|
18
|
-
default_labels.merge(custom_labels)
|
19
|
-
end
|
15
|
+
default_labels.merge(traefik_labels).merge(custom_labels)
|
20
16
|
end
|
21
17
|
|
22
18
|
def label_args
|
23
19
|
argumentize "--label", labels
|
24
20
|
end
|
25
21
|
|
22
|
+
def env
|
23
|
+
if config.env && config.env["secret"]
|
24
|
+
merged_env_with_secrets
|
25
|
+
else
|
26
|
+
merged_env
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def env_args
|
31
|
+
argumentize_env_with_secrets env
|
32
|
+
end
|
33
|
+
|
26
34
|
def cmd
|
27
35
|
specializations["cmd"]
|
28
36
|
end
|
29
37
|
|
38
|
+
def running_traefik?
|
39
|
+
name.web? || specializations["traefik"]
|
40
|
+
end
|
41
|
+
|
30
42
|
private
|
31
43
|
attr_accessor :config
|
32
44
|
|
@@ -44,13 +56,17 @@ class Mrsk::Configuration::Role
|
|
44
56
|
end
|
45
57
|
|
46
58
|
def traefik_labels
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
59
|
+
if running_traefik?
|
60
|
+
{
|
61
|
+
"traefik.http.routers.#{config.service}.rule" => "'PathPrefix(`/`)'",
|
62
|
+
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => "/up",
|
63
|
+
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
|
64
|
+
"traefik.http.middlewares.#{config.service}.retry.attempts" => "3",
|
65
|
+
"traefik.http.middlewares.#{config.service}.retry.initialinterval" => "500ms"
|
66
|
+
}
|
67
|
+
else
|
68
|
+
{}
|
69
|
+
end
|
54
70
|
end
|
55
71
|
|
56
72
|
def custom_labels
|
@@ -64,7 +80,23 @@ class Mrsk::Configuration::Role
|
|
64
80
|
if config.servers.is_a?(Array) || config.servers[name].is_a?(Array)
|
65
81
|
{ }
|
66
82
|
else
|
67
|
-
config.servers[name].
|
83
|
+
config.servers[name].except("hosts")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def specialized_env
|
88
|
+
specializations["env"] || {}
|
89
|
+
end
|
90
|
+
|
91
|
+
def merged_env
|
92
|
+
config.env&.merge(specialized_env) || {}
|
93
|
+
end
|
94
|
+
|
95
|
+
# Secrets are stored in an array, which won't merge by default, so have to do it by hand.
|
96
|
+
def merged_env_with_secrets
|
97
|
+
merged_env.tap do |new_env|
|
98
|
+
new_env["secret"] = Array(config.env["secret"]) + Array(specialized_env["secret"])
|
99
|
+
new_env["clear"] = (Array(config.env["clear"] || config.env) + Array(specialized_env["clear"] || specialized_env)).uniq
|
68
100
|
end
|
69
101
|
end
|
70
102
|
end
|
data/lib/mrsk/configuration.rb
CHANGED
@@ -3,26 +3,42 @@ require "active_support/core_ext/string/inquiry"
|
|
3
3
|
require "active_support/core_ext/module/delegation"
|
4
4
|
require "pathname"
|
5
5
|
require "erb"
|
6
|
+
require "mrsk/utils"
|
6
7
|
|
7
8
|
class Mrsk::Configuration
|
8
|
-
delegate :service, :image, :servers, :env, :labels, :registry, :builder, to: :
|
9
|
+
delegate :service, :image, :servers, :env, :labels, :registry, :builder, to: :raw_config, allow_nil: true
|
10
|
+
delegate :argumentize, :argumentize_env_with_secrets, to: Mrsk::Utils
|
11
|
+
|
12
|
+
attr_accessor :raw_config
|
9
13
|
|
10
14
|
class << self
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
def create_from(base_config_file, destination: nil, version: "missing")
|
16
|
+
new(load_config_file(base_config_file).tap do |config|
|
17
|
+
if destination
|
18
|
+
config.merge! \
|
19
|
+
load_config_file destination_config_file(base_config_file, destination)
|
20
|
+
end
|
21
|
+
end, version: version)
|
17
22
|
end
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
|
24
|
+
private
|
25
|
+
def load_config_file(file)
|
26
|
+
if file.exist?
|
27
|
+
YAML.load(ERB.new(IO.read(file)).result).symbolize_keys
|
28
|
+
else
|
29
|
+
raise "Configuration file not found in #{file}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def destination_config_file(base_config_file, destination)
|
34
|
+
dir, basename = base_config_file.split
|
35
|
+
dir.join basename.to_s.remove(".yml") + ".#{destination}.yml"
|
36
|
+
end
|
22
37
|
end
|
23
38
|
|
24
|
-
def initialize(
|
25
|
-
@
|
39
|
+
def initialize(raw_config, version: "missing", validate: true)
|
40
|
+
@raw_config = ActiveSupport::InheritableOptions.new(raw_config)
|
41
|
+
@version = version
|
26
42
|
ensure_required_keys_present if validate
|
27
43
|
end
|
28
44
|
|
@@ -35,36 +51,34 @@ class Mrsk::Configuration
|
|
35
51
|
roles.detect { |r| r.name == name.to_s }
|
36
52
|
end
|
37
53
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
when ENV["HOSTS"]
|
42
|
-
ENV["HOSTS"].split(",")
|
43
|
-
when ENV["ROLES"]
|
44
|
-
role_names = ENV["ROLES"].split(",")
|
45
|
-
roles.select { |r| role_names.include?(r.name) }.flat_map(&:hosts)
|
46
|
-
else
|
47
|
-
roles.flat_map(&:hosts)
|
48
|
-
end
|
54
|
+
def accessories
|
55
|
+
@accessories ||= raw_config.accessories.keys.collect { |name| Mrsk::Configuration::Assessory.new(name, config: self) }
|
56
|
+
end
|
49
57
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
58
|
+
def accessory(name)
|
59
|
+
accessories.detect { |a| a.name == name.to_s }
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def all_hosts
|
64
|
+
roles.flat_map(&:hosts)
|
55
65
|
end
|
56
66
|
|
57
|
-
def
|
67
|
+
def primary_web_host
|
58
68
|
role(:web).hosts.first
|
59
69
|
end
|
60
70
|
|
71
|
+
def traefik_hosts
|
72
|
+
roles.select(&:running_traefik?).flat_map(&:hosts)
|
73
|
+
end
|
74
|
+
|
61
75
|
|
62
76
|
def version
|
63
|
-
@version
|
77
|
+
@version
|
64
78
|
end
|
65
79
|
|
66
80
|
def repository
|
67
|
-
[
|
81
|
+
[ raw_config.registry["server"], image ].compact.join("/")
|
68
82
|
end
|
69
83
|
|
70
84
|
def absolute_image
|
@@ -77,15 +91,23 @@ class Mrsk::Configuration
|
|
77
91
|
|
78
92
|
|
79
93
|
def env_args
|
80
|
-
if
|
81
|
-
|
94
|
+
if raw_config.env.present?
|
95
|
+
argumentize_env_with_secrets(raw_config.env)
|
96
|
+
else
|
97
|
+
[]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def volume_args
|
102
|
+
if raw_config.volumes.present?
|
103
|
+
argumentize "--volume", raw_config.volumes
|
82
104
|
else
|
83
105
|
[]
|
84
106
|
end
|
85
107
|
end
|
86
108
|
|
87
109
|
def ssh_user
|
88
|
-
|
110
|
+
raw_config.ssh_user || "root"
|
89
111
|
end
|
90
112
|
|
91
113
|
def ssh_options
|
@@ -96,23 +118,42 @@ class Mrsk::Configuration
|
|
96
118
|
ENV["RAILS_MASTER_KEY"] || File.read(Pathname.new(File.expand_path("config/master.key")))
|
97
119
|
end
|
98
120
|
|
121
|
+
def to_h
|
122
|
+
{
|
123
|
+
roles: role_names,
|
124
|
+
hosts: all_hosts,
|
125
|
+
primary_host: primary_web_host,
|
126
|
+
version: version,
|
127
|
+
repository: repository,
|
128
|
+
absolute_image: absolute_image,
|
129
|
+
service_with_version: service_with_version,
|
130
|
+
env_args: env_args,
|
131
|
+
volume_args: volume_args,
|
132
|
+
ssh_options: ssh_options,
|
133
|
+
builder: raw_config.builder
|
134
|
+
}.compact
|
135
|
+
end
|
99
136
|
|
100
|
-
private
|
101
|
-
attr_accessor :config
|
102
137
|
|
138
|
+
private
|
103
139
|
def ensure_required_keys_present
|
104
|
-
%i[ service image registry ].each do |key|
|
105
|
-
raise ArgumentError, "Missing required configuration for #{key}" unless
|
140
|
+
%i[ service image registry servers ].each do |key|
|
141
|
+
raise ArgumentError, "Missing required configuration for #{key}" unless raw_config[key].present?
|
106
142
|
end
|
107
143
|
|
108
|
-
|
109
|
-
raise ArgumentError, "
|
110
|
-
end
|
144
|
+
if raw_config.registry["username"].blank?
|
145
|
+
raise ArgumentError, "You must specify a username for the registry in config/deploy.yml"
|
146
|
+
end
|
147
|
+
|
148
|
+
if raw_config.registry["password"].blank?
|
149
|
+
raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
|
150
|
+
end
|
111
151
|
end
|
112
152
|
|
113
153
|
def role_names
|
114
|
-
|
154
|
+
raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
|
115
155
|
end
|
116
156
|
end
|
117
157
|
|
118
158
|
require "mrsk/configuration/role"
|
159
|
+
require "mrsk/configuration/accessory"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "sshkit"
|
2
|
+
require "sshkit/dsl"
|
3
|
+
|
4
|
+
class SSHKit::Backend::Abstract
|
5
|
+
def capture_with_info(*args)
|
6
|
+
capture(*args, verbosity: Logger::INFO)
|
7
|
+
end
|
8
|
+
|
9
|
+
def puts_by_host(host, output, type: "App")
|
10
|
+
puts "#{type} Host: #{host}\n#{output}\n\n"
|
11
|
+
end
|
12
|
+
end
|
data/lib/mrsk/utils.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Mrsk::Utils
|
2
|
+
extend self
|
3
|
+
|
4
|
+
# Return a list of shell arguments using the same named argument against the passed attributes (hash or array).
|
5
|
+
def argumentize(argument, attributes, redacted: false)
|
6
|
+
Array(attributes).flat_map do |k, v|
|
7
|
+
if v.present?
|
8
|
+
[ argument, redacted ? redact("#{k}=#{v}") : "#{k}=#{v}" ]
|
9
|
+
else
|
10
|
+
[ argument, k ]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return a list of shell arguments using the same named argument against the passed attributes,
|
16
|
+
# but redacts and expands secrets.
|
17
|
+
def argumentize_env_with_secrets(env)
|
18
|
+
if (secrets = env["secret"]).present?
|
19
|
+
argumentize("-e", secrets.to_h { |key| [ key, ENV.fetch(key) ] }, redacted: true) + argumentize("-e", env["clear"])
|
20
|
+
else
|
21
|
+
argumentize "-e", env
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Copied from SSHKit::Backend::Abstract#redact to be available inside Commands classes
|
26
|
+
def redact(arg) # Used in execute_command to hide redact() args a user passes in
|
27
|
+
arg.to_s.extend(SSHKit::Redaction) # to_s due to our inability to extend Integer, etc
|
28
|
+
end
|
29
|
+
end
|
data/lib/mrsk/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mrsk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -64,6 +64,7 @@ files:
|
|
64
64
|
- bin/mrsk
|
65
65
|
- lib/mrsk.rb
|
66
66
|
- lib/mrsk/cli.rb
|
67
|
+
- lib/mrsk/cli/accessory.rb
|
67
68
|
- lib/mrsk/cli/app.rb
|
68
69
|
- lib/mrsk/cli/base.rb
|
69
70
|
- lib/mrsk/cli/build.rb
|
@@ -75,17 +76,23 @@ files:
|
|
75
76
|
- lib/mrsk/cli/traefik.rb
|
76
77
|
- lib/mrsk/commander.rb
|
77
78
|
- lib/mrsk/commands.rb
|
79
|
+
- lib/mrsk/commands/accessory.rb
|
78
80
|
- lib/mrsk/commands/app.rb
|
79
81
|
- lib/mrsk/commands/base.rb
|
80
82
|
- lib/mrsk/commands/builder.rb
|
83
|
+
- lib/mrsk/commands/builder/base.rb
|
81
84
|
- lib/mrsk/commands/builder/multiarch.rb
|
82
85
|
- lib/mrsk/commands/builder/multiarch/remote.rb
|
83
86
|
- lib/mrsk/commands/builder/native.rb
|
87
|
+
- lib/mrsk/commands/builder/native/remote.rb
|
84
88
|
- lib/mrsk/commands/prune.rb
|
85
89
|
- lib/mrsk/commands/registry.rb
|
86
90
|
- lib/mrsk/commands/traefik.rb
|
87
91
|
- lib/mrsk/configuration.rb
|
92
|
+
- lib/mrsk/configuration/accessory.rb
|
88
93
|
- lib/mrsk/configuration/role.rb
|
94
|
+
- lib/mrsk/sshkit_with_ext.rb
|
95
|
+
- lib/mrsk/utils.rb
|
89
96
|
- lib/mrsk/version.rb
|
90
97
|
homepage: https://github.com/rails/mrsk
|
91
98
|
licenses:
|