mrsk 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +21 -0
- data/bin/mrsk +4 -0
- data/lib/mrsk/cli/accessory.rb +23 -5
- data/lib/mrsk/cli/app.rb +1 -1
- data/lib/mrsk/cli/base.rb +10 -1
- data/lib/mrsk/cli/build.rb +2 -3
- data/lib/mrsk/cli/traefik.rb +1 -1
- data/lib/mrsk/commander.rb +5 -5
- data/lib/mrsk/commands/accessory.rb +12 -10
- data/lib/mrsk/commands/app.rb +2 -5
- data/lib/mrsk/commands/base.rb +4 -4
- data/lib/mrsk/commands/traefik.rb +8 -2
- data/lib/mrsk/configuration/accessory.rb +8 -1
- data/lib/mrsk/configuration.rb +18 -1
- data/lib/mrsk/utils.rb +1 -1
- data/lib/mrsk/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b7f4333081763cf001d3891931c1615eeb46bc29c2a8754ec863db67259f542
|
4
|
+
data.tar.gz: 809cc3d35b9c9f445299f3e188cd74b32e504dd7747d7dfcd0b11a698b34ebf9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 675b6e985621e236c9a3cde0b19d245bb8ac68ce9ae0ceef68c574c3427b1d8255bcd84f4547f9329eca535994a66b6cc6e88470181ca3ef74ea4e06e1a88244
|
7
|
+
data.tar.gz: 701df3deb670cfcd323a57d0baa5ded62e267657a7ad2198d226c809b89ace6eda46a282146d18123b569db4ef72d996ea487fd157ec8f0f7b639dbb56bf1fa4
|
data/README.md
CHANGED
@@ -46,6 +46,15 @@ Kubernetes is a beast. Running it yourself on your own hardware is not for the f
|
|
46
46
|
|
47
47
|
## Configuration
|
48
48
|
|
49
|
+
### Using .env file to load required environment variables
|
50
|
+
|
51
|
+
MRSK uses [dotenv](https://github.com/bkeepers/dotenv) to automatically load environment variables set in the `.env` file present in the application root. This file can be used to set variables like `MRSK_REGISTRY_PASSWORD` or database passwords. But for this reason you must ensure that .env files are not checked into Git or included in your Dockerfile! The format is just key-value like:
|
52
|
+
|
53
|
+
```bash
|
54
|
+
MRSK_REGISTRY_PASSWORD=pw
|
55
|
+
DB_PASSWORD=secret123
|
56
|
+
```
|
57
|
+
|
49
58
|
### Using another registry than Docker Hub
|
50
59
|
|
51
60
|
The default registry is Docker Hub, but you can change it using `registry/server`:
|
@@ -226,6 +235,18 @@ RUN --mount=type=secret,id=GITHUB_TOKEN \
|
|
226
235
|
bundle install
|
227
236
|
```
|
228
237
|
|
238
|
+
### Using command arguments for Traefik
|
239
|
+
|
240
|
+
You can customize the traefik command line:
|
241
|
+
|
242
|
+
```yaml
|
243
|
+
traefik:
|
244
|
+
accesslog: true
|
245
|
+
accesslog.format: json
|
246
|
+
metrics.prometheus: true
|
247
|
+
metrics.prometheus.buckets: 0.1,0.3,1.2,5.0
|
248
|
+
```
|
249
|
+
|
229
250
|
### Configuring build args for new images
|
230
251
|
|
231
252
|
Build arguments that aren't secret can also be configured:
|
data/bin/mrsk
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
# Prevent failures from being reported twice.
|
4
4
|
Thread.report_on_exception = false
|
5
5
|
|
6
|
+
require "dotenv/load"
|
6
7
|
require "mrsk/cli"
|
7
8
|
|
8
9
|
begin
|
@@ -10,4 +11,7 @@ begin
|
|
10
11
|
rescue SSHKit::Runner::ExecuteError => e
|
11
12
|
puts " \e[31mERROR (#{e.cause.class}): #{e.cause.message}\e[0m"
|
12
13
|
puts e.cause.backtrace if ENV["VERBOSE"]
|
14
|
+
rescue => e
|
15
|
+
puts " \e[31mERROR (#{e.class}): #{e.message}\e[0m"
|
16
|
+
puts e.backtrace if ENV["VERBOSE"]
|
13
17
|
end
|
data/lib/mrsk/cli/accessory.rb
CHANGED
@@ -83,11 +83,29 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
83
83
|
end
|
84
84
|
|
85
85
|
desc "exec [NAME] [CMD]", "Execute a custom command on accessory host"
|
86
|
-
option :
|
86
|
+
option :method, aliases: "-m", default: "exec", desc: "Execution method: [exec] perform inside container / [run] perform in new container / [ssh] perform over ssh"
|
87
87
|
def exec(name, cmd)
|
88
|
+
runner = \
|
89
|
+
case options[:method]
|
90
|
+
when "exec" then "exec"
|
91
|
+
when "run" then "run_exec"
|
92
|
+
when "ssh_exec" then "exec_over_ssh"
|
93
|
+
when "ssh_run" then "run_over_ssh"
|
94
|
+
else raise "Unknown method: #{options[:method]}"
|
95
|
+
end.inquiry
|
96
|
+
|
88
97
|
with_accessory(name) do |accessory|
|
89
|
-
runner
|
90
|
-
|
98
|
+
if runner.exec_over_ssh? || runner.run_over_ssh?
|
99
|
+
run_locally do
|
100
|
+
info "Launching command on #{accessory.host}"
|
101
|
+
exec accessory.send(runner, cmd)
|
102
|
+
end
|
103
|
+
else
|
104
|
+
on(accessory.host) do
|
105
|
+
info "Launching command on #{accessory.host}"
|
106
|
+
execute *accessory.send(runner, cmd)
|
107
|
+
end
|
108
|
+
end
|
91
109
|
end
|
92
110
|
end
|
93
111
|
|
@@ -96,7 +114,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
96
114
|
with_accessory(name) do |accessory|
|
97
115
|
run_locally do
|
98
116
|
info "Launching bash session on #{accessory.host}"
|
99
|
-
exec accessory.bash
|
117
|
+
exec accessory.bash
|
100
118
|
end
|
101
119
|
end
|
102
120
|
end
|
@@ -118,7 +136,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
118
136
|
end
|
119
137
|
else
|
120
138
|
since = options[:since]
|
121
|
-
lines = options[:lines]
|
139
|
+
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
122
140
|
|
123
141
|
on(accessory.host) do
|
124
142
|
puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep))
|
data/lib/mrsk/cli/app.rb
CHANGED
@@ -109,7 +109,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
109
109
|
end
|
110
110
|
else
|
111
111
|
since = options[:since]
|
112
|
-
lines = options[:lines]
|
112
|
+
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
113
113
|
|
114
114
|
on(MRSK.hosts) do |host|
|
115
115
|
begin
|
data/lib/mrsk/cli/base.rb
CHANGED
@@ -8,6 +8,7 @@ module Mrsk::Cli
|
|
8
8
|
def self.exit_on_failure?() true end
|
9
9
|
|
10
10
|
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
|
11
|
+
class_option :quiet, type: :boolean, aliases: "-q", desc: "Minimal logging"
|
11
12
|
|
12
13
|
class_option :version, desc: "Run commands against a specific app version"
|
13
14
|
|
@@ -28,12 +29,20 @@ module Mrsk::Cli
|
|
28
29
|
MRSK.tap do |commander|
|
29
30
|
commander.config_file = Pathname.new(File.expand_path(options[:config_file]))
|
30
31
|
commander.destination = options[:destination]
|
31
|
-
commander.verbose = options[:verbose]
|
32
32
|
commander.version = options[:version]
|
33
33
|
|
34
34
|
commander.specific_hosts = options[:hosts]&.split(",")
|
35
35
|
commander.specific_roles = options[:roles]&.split(",")
|
36
36
|
commander.specific_primary! if options[:primary]
|
37
|
+
|
38
|
+
if options[:verbose]
|
39
|
+
ENV["VERBOSE"] = "1" # For backtraces via cli/start
|
40
|
+
commander.verbosity = :debug
|
41
|
+
end
|
42
|
+
|
43
|
+
if options[:quiet]
|
44
|
+
commander.verbosity = :error
|
45
|
+
end
|
37
46
|
end
|
38
47
|
end
|
39
48
|
|
data/lib/mrsk/cli/build.rb
CHANGED
@@ -9,18 +9,17 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
|
9
9
|
|
10
10
|
desc "push", "Build locally and push app image to registry"
|
11
11
|
def push
|
12
|
-
verbose = options[:verbose]
|
13
12
|
cli = self
|
14
13
|
|
15
14
|
run_locally do
|
16
15
|
begin
|
17
|
-
MRSK.
|
16
|
+
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
|
18
17
|
rescue SSHKit::Command::Failed => e
|
19
18
|
if e.message =~ /(no builder)|(no such file or directory)/
|
20
19
|
error "Missing compatible builder, so creating a new one first"
|
21
20
|
|
22
21
|
if cli.create
|
23
|
-
MRSK.
|
22
|
+
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
|
24
23
|
end
|
25
24
|
else
|
26
25
|
raise
|
data/lib/mrsk/cli/traefik.rb
CHANGED
@@ -50,7 +50,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
|
50
50
|
end
|
51
51
|
else
|
52
52
|
since = options[:since]
|
53
|
-
lines = options[:lines]
|
53
|
+
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
54
54
|
|
55
55
|
on(MRSK.traefik_hosts) do |host|
|
56
56
|
puts_by_host host, capture(*MRSK.traefik.logs(since: since, lines: lines, grep: grep)), type: "Traefik"
|
data/lib/mrsk/commander.rb
CHANGED
@@ -9,10 +9,10 @@ require "mrsk/commands/traefik"
|
|
9
9
|
require "mrsk/commands/registry"
|
10
10
|
|
11
11
|
class Mrsk::Commander
|
12
|
-
attr_accessor :config_file, :destination, :
|
12
|
+
attr_accessor :config_file, :destination, :verbosity, :version
|
13
13
|
|
14
|
-
def initialize(config_file: nil, destination: nil,
|
15
|
-
@config_file, @destination, @
|
14
|
+
def initialize(config_file: nil, destination: nil, verbosity: :info)
|
15
|
+
@config_file, @destination, @verbosity = config_file, destination, verbosity
|
16
16
|
end
|
17
17
|
|
18
18
|
def config
|
@@ -78,7 +78,7 @@ class Mrsk::Commander
|
|
78
78
|
end
|
79
79
|
|
80
80
|
|
81
|
-
def
|
81
|
+
def with_verbosity(level)
|
82
82
|
old_level = SSHKit.config.output_verbosity
|
83
83
|
SSHKit.config.output_verbosity = level
|
84
84
|
yield
|
@@ -95,6 +95,6 @@ class Mrsk::Commander
|
|
95
95
|
def configure_sshkit_with(config)
|
96
96
|
SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = config.ssh_options }
|
97
97
|
SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
|
98
|
-
SSHKit.config.output_verbosity =
|
98
|
+
SSHKit.config.output_verbosity = verbosity
|
99
99
|
end
|
100
100
|
end
|
@@ -42,15 +42,13 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
|
42
42
|
def follow_logs(grep: nil)
|
43
43
|
run_over_ssh pipe(
|
44
44
|
docker(:logs, service_name, "-t", "-n", "10", "-f", "2>&1"),
|
45
|
-
(
|
46
|
-
).join(" ")
|
45
|
+
(%(grep "#{grep}") if grep)
|
46
|
+
).join(" ")
|
47
47
|
end
|
48
48
|
|
49
49
|
def exec(*command, interactive: false)
|
50
50
|
docker :exec,
|
51
51
|
("-it" if interactive),
|
52
|
-
*env_args,
|
53
|
-
*volume_args,
|
54
52
|
service_name,
|
55
53
|
*command
|
56
54
|
end
|
@@ -65,8 +63,16 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
|
65
63
|
*command
|
66
64
|
end
|
67
65
|
|
68
|
-
def
|
69
|
-
|
66
|
+
def run_over_ssh(command)
|
67
|
+
super command, host: host
|
68
|
+
end
|
69
|
+
|
70
|
+
def exec_over_ssh(*command)
|
71
|
+
run_over_ssh run_exec(*command, interactive: true).join(" ")
|
72
|
+
end
|
73
|
+
|
74
|
+
def bash
|
75
|
+
exec_over_ssh "bash"
|
70
76
|
end
|
71
77
|
|
72
78
|
def ensure_local_file_present(local_file)
|
@@ -96,10 +102,6 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
|
96
102
|
end
|
97
103
|
|
98
104
|
private
|
99
|
-
def exec_over_ssh(*command, host:)
|
100
|
-
run_over_ssh run_exec(*command, interactive: true).join(" "), host: host
|
101
|
-
end
|
102
|
-
|
103
105
|
def service_filter
|
104
106
|
[ "--filter", "label=service=#{service_name}" ]
|
105
107
|
end
|
data/lib/mrsk/commands/app.rb
CHANGED
@@ -35,16 +35,13 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
35
35
|
def logs(since: nil, lines: nil, grep: nil)
|
36
36
|
pipe \
|
37
37
|
current_container_id,
|
38
|
-
"xargs docker logs#{" --since #{since}" if since}#{" -n #{lines}" if lines}
|
38
|
+
"xargs docker logs#{" --since #{since}" if since}#{" -n #{lines}" if lines} 2>&1",
|
39
39
|
("grep '#{grep}'" if grep)
|
40
40
|
end
|
41
41
|
|
42
42
|
def exec(*command, interactive: false)
|
43
43
|
docker :exec,
|
44
44
|
("-it" if interactive),
|
45
|
-
*rails_master_key_arg,
|
46
|
-
*config.env_args,
|
47
|
-
*config.volume_args,
|
48
45
|
config.service_with_version,
|
49
46
|
*command
|
50
47
|
end
|
@@ -68,7 +65,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
68
65
|
run_over_ssh pipe(
|
69
66
|
current_container_id,
|
70
67
|
"xargs docker logs -t -n 10 -f 2>&1",
|
71
|
-
(
|
68
|
+
(%(grep "#{grep}") if grep)
|
72
69
|
).join(" "), host: host
|
73
70
|
end
|
74
71
|
|
data/lib/mrsk/commands/base.rb
CHANGED
@@ -8,6 +8,10 @@ module Mrsk::Commands
|
|
8
8
|
@config = config
|
9
9
|
end
|
10
10
|
|
11
|
+
def run_over_ssh(command, host:)
|
12
|
+
"ssh -t #{config.ssh_user}@#{host} '#{command}'"
|
13
|
+
end
|
14
|
+
|
11
15
|
private
|
12
16
|
def combine(*commands, by: "&&")
|
13
17
|
commands
|
@@ -27,9 +31,5 @@ module Mrsk::Commands
|
|
27
31
|
def docker(*args)
|
28
32
|
args.compact.unshift :docker
|
29
33
|
end
|
30
|
-
|
31
|
-
def run_over_ssh(command, host:)
|
32
|
-
"ssh -t #{config.ssh_user}@#{host} '#{command}'"
|
33
|
-
end
|
34
34
|
end
|
35
35
|
end
|
@@ -9,7 +9,8 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
9
9
|
"-v /var/run/docker.sock:/var/run/docker.sock",
|
10
10
|
"traefik",
|
11
11
|
"--providers.docker",
|
12
|
-
"--log.level=DEBUG"
|
12
|
+
"--log.level=DEBUG",
|
13
|
+
*cmd_args
|
13
14
|
end
|
14
15
|
|
15
16
|
def start
|
@@ -33,7 +34,7 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
33
34
|
def follow_logs(host:, grep: nil)
|
34
35
|
run_over_ssh pipe(
|
35
36
|
docker(:logs, "traefik", "-t", "-n", "10", "-f", "2>&1"),
|
36
|
-
(
|
37
|
+
(%(grep "#{grep}") if grep)
|
37
38
|
).join(" "), host: host
|
38
39
|
end
|
39
40
|
|
@@ -44,4 +45,9 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
44
45
|
def remove_image
|
45
46
|
docker :image, :prune, "-a", "-f", "--filter", "label=org.opencontainers.image.title=Traefik"
|
46
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def cmd_args
|
51
|
+
(config.raw_config.dig(:traefik, "args") || { }).collect { |(key, value)| [ "--#{key}", value ] }.flatten
|
52
|
+
end
|
47
53
|
end
|
@@ -74,12 +74,19 @@ class Mrsk::Configuration::Assessory
|
|
74
74
|
|
75
75
|
def expand_local_file(local_file)
|
76
76
|
if local_file.end_with?("erb")
|
77
|
-
read_dynamic_file(local_file)
|
77
|
+
with_clear_env_loaded { read_dynamic_file(local_file) }
|
78
78
|
else
|
79
79
|
Pathname.new(File.expand_path(local_file)).to_s
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
+
def with_clear_env_loaded
|
84
|
+
(env["clear"] || env).each { |k, v| ENV[k] = v }
|
85
|
+
yield
|
86
|
+
ensure
|
87
|
+
(env["clear"] || env).each { |k, v| ENV.delete(k) }
|
88
|
+
end
|
89
|
+
|
83
90
|
def read_dynamic_file(local_file)
|
84
91
|
StringIO.new(ERB.new(IO.read(local_file)).result)
|
85
92
|
end
|
data/lib/mrsk/configuration.rb
CHANGED
@@ -39,7 +39,7 @@ class Mrsk::Configuration
|
|
39
39
|
def initialize(raw_config, version: "missing", validate: true)
|
40
40
|
@raw_config = ActiveSupport::InheritableOptions.new(raw_config)
|
41
41
|
@version = version
|
42
|
-
|
42
|
+
valid? if validate
|
43
43
|
end
|
44
44
|
|
45
45
|
|
@@ -120,6 +120,12 @@ class Mrsk::Configuration
|
|
120
120
|
end
|
121
121
|
end
|
122
122
|
|
123
|
+
|
124
|
+
def valid?
|
125
|
+
ensure_required_keys_present && ensure_env_available
|
126
|
+
end
|
127
|
+
|
128
|
+
|
123
129
|
def to_h
|
124
130
|
{
|
125
131
|
roles: role_names,
|
@@ -139,6 +145,7 @@ class Mrsk::Configuration
|
|
139
145
|
|
140
146
|
|
141
147
|
private
|
148
|
+
# Will raise ArgumentError if any required config keys are missing
|
142
149
|
def ensure_required_keys_present
|
143
150
|
%i[ service image registry servers ].each do |key|
|
144
151
|
raise ArgumentError, "Missing required configuration for #{key}" unless raw_config[key].present?
|
@@ -151,6 +158,16 @@ class Mrsk::Configuration
|
|
151
158
|
if raw_config.registry["password"].blank?
|
152
159
|
raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
|
153
160
|
end
|
161
|
+
|
162
|
+
true
|
163
|
+
end
|
164
|
+
|
165
|
+
# Will raise KeyError if any secret ENVs are missing
|
166
|
+
def ensure_env_available
|
167
|
+
env_args
|
168
|
+
roles.each(&:env_args)
|
169
|
+
|
170
|
+
true
|
154
171
|
end
|
155
172
|
|
156
173
|
def role_names
|
data/lib/mrsk/utils.rb
CHANGED
@@ -18,7 +18,7 @@ module Mrsk::Utils
|
|
18
18
|
if (secrets = env["secret"]).present?
|
19
19
|
argumentize("-e", secrets.to_h { |key| [ key, ENV.fetch(key) ] }, redacted: true) + argumentize("-e", env["clear"])
|
20
20
|
else
|
21
|
-
argumentize "-e", env
|
21
|
+
argumentize "-e", env.fetch("clear", env)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
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.4.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-02-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: dotenv
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.8'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.8'
|
55
69
|
description:
|
56
70
|
email: dhh@hey.com
|
57
71
|
executables:
|