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 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: