mrsk 0.3.1 → 0.4.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: 7d7eec74db21b02c791939440655365ce5d24b9a75c6a79ffad1db2c6bb44ee8
4
- data.tar.gz: bbacf7f546dd7136b2d2412535a466f44b9000d74ed74cf3b1ddd0b3750728a1
3
+ metadata.gz: 1b7f4333081763cf001d3891931c1615eeb46bc29c2a8754ec863db67259f542
4
+ data.tar.gz: 809cc3d35b9c9f445299f3e188cd74b32e504dd7747d7dfcd0b11a698b34ebf9
5
5
  SHA512:
6
- metadata.gz: 919de205161fba4e0565b79fa67caf5439bfeefcac5fea4b36e48aac45c827922daf3c9ea2dba42cdfd55a954e9a33b58c46b087bb9f2e59b10071088f643b83
7
- data.tar.gz: 7af61fe9154261ade34923c0aeaf8f095f6e60cb8bf57f9aabea2488502b1c10b35ff3f2be4e5821aa5e31e59ec0b0d132949e8c9e85ee689e8465c85ae8fa05
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
@@ -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 :run, type: :boolean, default: false, desc: "Start a new container to run the command rather than reusing existing"
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 = options[:run] ? :run_exec : :exec
90
- on(accessory.host) { |host| puts_by_host host, capture_with_info(*accessory.send(runner, cmd)) }
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(host: accessory.host)
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
 
@@ -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.verbosity(:debug) { execute *MRSK.builder.push }
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.verbosity(:debug) { execute *MRSK.builder.push }
22
+ MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
24
23
  end
25
24
  else
26
25
  raise
@@ -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"
@@ -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, :verbose, :version
12
+ attr_accessor :config_file, :destination, :verbosity, :version
13
13
 
14
- def initialize(config_file: nil, destination: nil, verbose: false)
15
- @config_file, @destination, @verbose = config_file, destination, verbose
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 verbosity(level)
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 = :debug if verbose
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
- ("grep '#{grep}'" if grep)
46
- ).join(" "), host: host
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 bash(host:)
69
- exec_over_ssh "bash", host: host
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
@@ -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} -t 2>&1",
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
- ("grep '#{grep}'" if grep)
68
+ (%(grep "#{grep}") if grep)
72
69
  ).join(" "), host: host
73
70
  end
74
71
 
@@ -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
- ("grep '#{grep}'" if grep)
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
@@ -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
- ensure_required_keys_present if validate
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
@@ -1,3 +1,3 @@
1
1
  module Mrsk
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
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.3.1
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-27 00:00:00.000000000 Z
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: