mrsk 0.0.2 → 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.
@@ -0,0 +1,44 @@
1
+ require "mrsk/cli/base"
2
+
3
+ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
4
+ desc "boot", "Boot Traefik on servers"
5
+ def boot
6
+ on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false }
7
+ end
8
+
9
+ desc "start", "Start existing Traefik on servers"
10
+ def start
11
+ on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.start, raise_on_non_zero_exit: false }
12
+ end
13
+
14
+ desc "stop", "Stop Traefik on servers"
15
+ def stop
16
+ on(MRSK.config.role(:web).hosts) { execute *MRSK.traefik.stop, raise_on_non_zero_exit: false }
17
+ end
18
+
19
+ desc "restart", "Restart Traefik on servers"
20
+ def restart
21
+ invoke :stop
22
+ invoke :start
23
+ end
24
+
25
+ desc "details", "Display details about Traefik containers from servers"
26
+ def details
27
+ on(MRSK.config.role(:web).hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*MRSK.traefik.info, verbosity: Logger::INFO) + "\n\n" }
28
+ end
29
+
30
+ desc "logs", "Show last 100 log lines from Traefik on servers"
31
+ def logs
32
+ on(MRSK.config.hosts) { |host| puts "Traefik Host: #{host}\n" + capture(*MRSK.traefik.logs) + "\n\n" }
33
+ end
34
+
35
+ desc "remove", "Remove Traefik container and image from servers"
36
+ def remove
37
+ invoke :stop
38
+
39
+ on(MRSK.config.role(:web).hosts) do
40
+ execute *MRSK.traefik.remove_container
41
+ execute *MRSK.traefik.remove_image
42
+ end
43
+ end
44
+ end
data/lib/mrsk/cli.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "mrsk"
2
+
3
+ MRSK = Mrsk::Commander.new \
4
+ config_file: Pathname.new(File.expand_path("config/deploy.yml"))
5
+
6
+ module Mrsk::Cli
7
+ end
8
+
9
+ require "mrsk/cli/main"
@@ -0,0 +1,57 @@
1
+ require "mrsk/configuration"
2
+ require "mrsk/commands/app"
3
+ require "mrsk/commands/builder"
4
+ require "mrsk/commands/prune"
5
+ require "mrsk/commands/traefik"
6
+ require "mrsk/commands/registry"
7
+
8
+ class Mrsk::Commander
9
+ attr_reader :config
10
+ attr_accessor :verbose
11
+
12
+ def initialize(config_file:)
13
+ @config_file = config_file
14
+ end
15
+
16
+ def config
17
+ @config ||= Mrsk::Configuration.load_file(@config_file).tap { |config| setup_with(config) }
18
+ end
19
+
20
+
21
+ def app
22
+ @app ||= Mrsk::Commands::App.new(config)
23
+ end
24
+
25
+ def builder
26
+ @builder ||= Mrsk::Commands::Builder.new(config)
27
+ end
28
+
29
+ def traefik
30
+ @traefik ||= Mrsk::Commands::Traefik.new(config)
31
+ end
32
+
33
+ def registry
34
+ @registry ||= Mrsk::Commands::Registry.new(config)
35
+ end
36
+
37
+ def prune
38
+ @prune ||= Mrsk::Commands::Prune.new(config)
39
+ end
40
+
41
+
42
+ def verbosity(level)
43
+ old_level = SSHKit.config.output_verbosity
44
+ SSHKit.config.output_verbosity = level
45
+ yield
46
+ ensure
47
+ SSHKit.config.output_verbosity = old_level
48
+ end
49
+
50
+ private
51
+ # Lazy setup of SSHKit
52
+ def setup_with(config)
53
+ SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = config.ssh_options }
54
+ SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
55
+ SSHKit.config.output_verbosity = :debug if verbose
56
+ end
57
+ end
@@ -1,14 +1,6 @@
1
- class Mrsk::Commands::App < Mrsk::Commands::Base
2
- def push
3
- # TODO: Run 'docker buildx create --use' when needed
4
- # TODO: Make multiarch an option so Linux users can enjoy speedier builds
5
- docker :buildx, :build, "--push", "--platform linux/amd64,linux/arm64", "-t", config.absolute_image, "."
6
- end
7
-
8
- def pull
9
- docker :pull, config.absolute_image
10
- end
1
+ require "mrsk/commands/base"
11
2
 
3
+ class Mrsk::Commands::App < Mrsk::Commands::Base
12
4
  def run(role: :web)
13
5
  role = config.role(role)
14
6
 
@@ -23,8 +15,8 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
23
15
  role.cmd
24
16
  end
25
17
 
26
- def start
27
- docker :start, config.service_with_version
18
+ def start(version: config.version)
19
+ docker :start, "#{config.service}-#{version}"
28
20
  end
29
21
 
30
22
  def stop
@@ -36,17 +28,22 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
36
28
  end
37
29
 
38
30
  def logs
39
- [ "docker ps -q #{service_filter.join(" ")} | xargs docker logs -f" ]
31
+ [ "docker ps -q #{service_filter.join(" ")} | xargs docker logs -n 100 -t" ]
40
32
  end
41
33
 
42
- def exec(*command)
43
- docker :exec,
34
+ def exec(*command, interactive: false)
35
+ docker :exec,
36
+ ("-it" if interactive),
44
37
  "-e", redact("RAILS_MASTER_KEY=#{config.master_key}"),
45
38
  *config.env_args,
46
39
  config.service_with_version,
47
40
  *command
48
41
  end
49
42
 
43
+ def console(host: config.primary_host)
44
+ "ssh -t #{config.ssh_user}@#{host} '#{exec("bin/rails", "c", interactive: true).join(" ")}'"
45
+ end
46
+
50
47
  def list_containers
51
48
  docker :container, :ls, "-a", *service_filter
52
49
  end
@@ -0,0 +1,27 @@
1
+ require "sshkit"
2
+
3
+ module Mrsk::Commands
4
+ class Base
5
+ attr_accessor :config
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ private
12
+ def combine(*commands)
13
+ commands
14
+ .collect { |command| command + [ "&&" ] }.flatten # Join commands with &&
15
+ .tap { |commands| commands.pop } # Remove trailing &&
16
+ end
17
+
18
+ def docker(*args)
19
+ args.compact.unshift :docker
20
+ end
21
+
22
+ # Copied from SSHKit::Backend::Abstract#redact to be available inside Commands classes
23
+ def redact(arg) # Used in execute_command to hide redact() args a user passes in
24
+ arg.to_s.extend(SSHKit::Redaction) # to_s due to our inability to extend Integer, etc
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,58 @@
1
+ require "mrsk/commands/builder/multiarch"
2
+
3
+ class Mrsk::Commands::Builder::Multiarch::Remote < Mrsk::Commands::Builder::Multiarch
4
+ def create
5
+ combine \
6
+ create_contexts,
7
+ create_local_buildx,
8
+ append_remote_buildx
9
+ end
10
+
11
+ def remove
12
+ combine \
13
+ remove_contexts,
14
+ super
15
+ end
16
+
17
+ private
18
+ def create_local_buildx
19
+ docker :buildx, :create, "--use", "--name", builder_name, builder_name_with_arch(local["arch"]), "--platform", "linux/#{local["arch"]}"
20
+ end
21
+
22
+ def append_remote_buildx
23
+ docker :buildx, :create, "--append", "--name", builder_name, builder_name_with_arch(remote["arch"]), "--platform", "linux/#{remote["arch"]}"
24
+ end
25
+
26
+ def create_contexts
27
+ combine \
28
+ create_context(local["arch"], local["host"]),
29
+ create_context(remote["arch"], remote["host"])
30
+ end
31
+
32
+ def create_context(arch, host)
33
+ docker :context, :create, builder_name_with_arch(arch), "--description", "'#{builder_name} #{arch} native host'", "--docker", "'host=#{host}'"
34
+ end
35
+
36
+ def remove_contexts
37
+ combine \
38
+ remove_context(local["arch"]),
39
+ remove_context(remote["arch"])
40
+ end
41
+
42
+ def remove_context(arch)
43
+ docker :context, :rm, builder_name_with_arch(arch)
44
+ end
45
+
46
+ def local
47
+ config.builder["local"]
48
+ end
49
+
50
+ def remote
51
+ config.builder["remote"]
52
+ end
53
+
54
+ private
55
+ def builder_name_with_arch(arch)
56
+ "#{builder_name}-#{arch}"
57
+ end
58
+ end
@@ -0,0 +1,30 @@
1
+ require "mrsk/commands/base"
2
+
3
+ class Mrsk::Commands::Builder::Multiarch < Mrsk::Commands::Base
4
+ def create
5
+ docker :buildx, :create, "--use", "--name", builder_name
6
+ end
7
+
8
+ def remove
9
+ docker :buildx, :rm, builder_name
10
+ end
11
+
12
+ def push
13
+ docker :buildx, :build, "--push", "--platform linux/amd64,linux/arm64", "-t", config.absolute_image, "."
14
+ end
15
+
16
+ def pull
17
+ docker :pull, config.absolute_image
18
+ end
19
+
20
+ def info
21
+ combine \
22
+ docker(:context, :ls),
23
+ docker(:buildx, :ls)
24
+ end
25
+
26
+ private
27
+ def builder_name
28
+ "mrsk-#{config.service}"
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ require "mrsk/commands/base"
2
+
3
+ class Mrsk::Commands::Builder::Native < Mrsk::Commands::Base
4
+ def create
5
+ # No-op on native
6
+ end
7
+
8
+ def remove
9
+ # No-op on native
10
+ end
11
+
12
+ def push
13
+ combine \
14
+ docker(:build, "-t", config.absolute_image, "."),
15
+ docker(:push, config.absolute_image)
16
+ end
17
+
18
+ def pull
19
+ docker :pull, config.absolute_image
20
+ end
21
+
22
+ def info
23
+ # No-op on native
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ require "mrsk/commands/base"
2
+
3
+ class Mrsk::Commands::Builder < Mrsk::Commands::Base
4
+ delegate :create, :remove, :push, :pull, :info, to: :target
5
+ delegate :native?, :multiarch?, :remote?, to: :name
6
+
7
+ def name
8
+ target.class.to_s.demodulize.downcase.inquiry
9
+ end
10
+
11
+ def target
12
+ case
13
+ when config.builder.nil?
14
+ multiarch
15
+ when config.builder["multiarch"] == false
16
+ native
17
+ when config.builder["local"] && config.builder["local"]
18
+ multiarch_remote
19
+ else
20
+ raise ArgumentError, "Builder configuration incorrect: #{config.builder.inspect}"
21
+ end
22
+ end
23
+
24
+ def native
25
+ @native ||= Mrsk::Commands::Builder::Native.new(config)
26
+ end
27
+
28
+ def multiarch
29
+ @multiarch ||= Mrsk::Commands::Builder::Multiarch.new(config)
30
+ end
31
+
32
+ def multiarch_remote
33
+ @multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config)
34
+ end
35
+ end
36
+
37
+ require "mrsk/commands/builder/native"
38
+ require "mrsk/commands/builder/multiarch"
39
+ require "mrsk/commands/builder/multiarch/remote"
@@ -0,0 +1,17 @@
1
+ require "mrsk/commands/base"
2
+ require "active_support/duration"
3
+ require "active_support/core_ext/numeric/time"
4
+
5
+ class Mrsk::Commands::Prune < Mrsk::Commands::Base
6
+ PRUNE_IMAGES_AFTER = 30.days.in_hours.to_i
7
+ PRUNE_CONTAINERS_AFTER = 3.days.in_hours.to_i
8
+
9
+ def images
10
+ docker :image, :prune, "-f", "--filter", "until=#{PRUNE_IMAGES_AFTER}h"
11
+ end
12
+
13
+ def containers
14
+ docker :image, :prune, "-f", "--filter", "until=#{PRUNE_IMAGES_AFTER}h"
15
+ docker :container, :prune, "-f", "--filter", "label=service=#{config.service}", "--filter", "'until=#{PRUNE_CONTAINERS_AFTER}h'"
16
+ end
17
+ end
@@ -1,3 +1,5 @@
1
+ require "mrsk/commands/base"
2
+
1
3
  class Mrsk::Commands::Registry < Mrsk::Commands::Base
2
4
  delegate :registry, to: :config
3
5
 
@@ -1,3 +1,5 @@
1
+ require "mrsk/commands/base"
2
+
1
3
  class Mrsk::Commands::Traefik < Mrsk::Commands::Base
2
4
  def run
3
5
  docker :run, "--name traefik",
@@ -22,7 +24,7 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
22
24
  end
23
25
 
24
26
  def logs
25
- docker :logs, "traefik"
27
+ docker :logs, "traefik", "-n", "100", "-t"
26
28
  end
27
29
 
28
30
  def remove_container
data/lib/mrsk/commands.rb CHANGED
@@ -1,25 +1,2 @@
1
- require "sshkit"
2
-
3
1
  module Mrsk::Commands
4
- class Base
5
- attr_accessor :config
6
-
7
- def initialize(config)
8
- @config = config
9
- end
10
-
11
- private
12
- def docker(*args)
13
- args.compact.unshift :docker
14
- end
15
-
16
- # Copied from SSHKit::Backend::Abstract#redact to be available inside Commands classes
17
- def redact(arg) # Used in execute_command to hide redact() args a user passes in
18
- arg.to_s.extend(SSHKit::Redaction) # to_s due to our inability to extend Integer, etc
19
- end
20
- end
21
2
  end
22
-
23
- require "mrsk/commands/app"
24
- require "mrsk/commands/traefik"
25
- require "mrsk/commands/registry"
@@ -11,6 +11,14 @@ class Mrsk::Configuration::Role
11
11
  @hosts ||= extract_hosts_from_config
12
12
  end
13
13
 
14
+ def labels
15
+ if name.web?
16
+ default_labels.merge(traefik_labels).merge(custom_labels)
17
+ else
18
+ default_labels.merge(custom_labels)
19
+ end
20
+ end
21
+
14
22
  def label_args
15
23
  argumentize "--label", labels
16
24
  end
@@ -31,14 +39,6 @@ class Mrsk::Configuration::Role
31
39
  end
32
40
  end
33
41
 
34
- def labels
35
- if name.web?
36
- default_labels.merge(traefik_labels)
37
- else
38
- default_labels
39
- end
40
- end
41
-
42
42
  def default_labels
43
43
  { "service" => config.service, "role" => name }
44
44
  end
@@ -53,6 +53,13 @@ class Mrsk::Configuration::Role
53
53
  }
54
54
  end
55
55
 
56
+ def custom_labels
57
+ Hash.new.tap do |labels|
58
+ labels.merge!(config.labels) if config.labels.present?
59
+ labels.merge!(specializations["labels"]) if specializations["labels"].present?
60
+ end
61
+ end
62
+
56
63
  def specializations
57
64
  if config.servers.is_a?(Array) || config.servers[name].is_a?(Array)
58
65
  { }
@@ -1,9 +1,11 @@
1
1
  require "active_support/ordered_options"
2
2
  require "active_support/core_ext/string/inquiry"
3
+ require "active_support/core_ext/module/delegation"
4
+ require "pathname"
3
5
  require "erb"
4
6
 
5
7
  class Mrsk::Configuration
6
- delegate :service, :image, :servers, :env, :registry, :ssh_user, to: :config, allow_nil: true
8
+ delegate :service, :image, :servers, :env, :labels, :registry, :builder, to: :config, allow_nil: true
7
9
 
8
10
  class << self
9
11
  def load_file(file)
@@ -75,15 +77,23 @@ class Mrsk::Configuration
75
77
 
76
78
 
77
79
  def env_args
78
- self.class.argumentize "-e", config.env if config.env.present?
80
+ if config.env.present?
81
+ self.class.argumentize "-e", config.env
82
+ else
83
+ []
84
+ end
85
+ end
86
+
87
+ def ssh_user
88
+ config.ssh_user || "root"
79
89
  end
80
90
 
81
91
  def ssh_options
82
- { user: config.ssh_user || "root", auth_methods: [ "publickey" ] }
92
+ { user: ssh_user, auth_methods: [ "publickey" ] }
83
93
  end
84
94
 
85
95
  def master_key
86
- ENV["RAILS_MASTER_KEY"] || File.read(Rails.root.join("config/master.key"))
96
+ ENV["RAILS_MASTER_KEY"] || File.read(Pathname.new(File.expand_path("config/master.key")))
87
97
  end
88
98
 
89
99
 
data/lib/mrsk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mrsk
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/mrsk.rb CHANGED
@@ -2,7 +2,4 @@ module Mrsk
2
2
  end
3
3
 
4
4
  require "mrsk/version"
5
- require "mrsk/engine"
6
-
7
- require "mrsk/configuration"
8
- require "mrsk/commands"
5
+ require "mrsk/commander"
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mrsk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.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-10 00:00:00.000000000 Z
11
+ date: 2023-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: railties
14
+ name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 7.0.0
19
+ version: '7.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 7.0.0
26
+ version: '7.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: sshkit
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,31 +38,55 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.21'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
41
55
  description:
42
56
  email: dhh@hey.com
43
- executables: []
57
+ executables:
58
+ - mrsk
44
59
  extensions: []
45
60
  extra_rdoc_files: []
46
61
  files:
47
62
  - MIT-LICENSE
48
63
  - README.md
64
+ - bin/mrsk
49
65
  - lib/mrsk.rb
66
+ - lib/mrsk/cli.rb
67
+ - lib/mrsk/cli/app.rb
68
+ - lib/mrsk/cli/base.rb
69
+ - lib/mrsk/cli/build.rb
70
+ - lib/mrsk/cli/main.rb
71
+ - lib/mrsk/cli/prune.rb
72
+ - lib/mrsk/cli/registry.rb
73
+ - lib/mrsk/cli/server.rb
74
+ - lib/mrsk/cli/templates/deploy.yml
75
+ - lib/mrsk/cli/traefik.rb
76
+ - lib/mrsk/commander.rb
50
77
  - lib/mrsk/commands.rb
51
78
  - lib/mrsk/commands/app.rb
79
+ - lib/mrsk/commands/base.rb
80
+ - lib/mrsk/commands/builder.rb
81
+ - lib/mrsk/commands/builder/multiarch.rb
82
+ - lib/mrsk/commands/builder/multiarch/remote.rb
83
+ - lib/mrsk/commands/builder/native.rb
84
+ - lib/mrsk/commands/prune.rb
52
85
  - lib/mrsk/commands/registry.rb
53
86
  - lib/mrsk/commands/traefik.rb
54
87
  - lib/mrsk/configuration.rb
55
88
  - lib/mrsk/configuration/role.rb
56
- - lib/mrsk/engine.rb
57
89
  - lib/mrsk/version.rb
58
- - lib/tasks/mrsk/app.rake
59
- - lib/tasks/mrsk/mrsk.rake
60
- - lib/tasks/mrsk/prune.rake
61
- - lib/tasks/mrsk/registry.rake
62
- - lib/tasks/mrsk/server.rake
63
- - lib/tasks/mrsk/setup.rb
64
- - lib/tasks/mrsk/templates/deploy.yml
65
- - lib/tasks/mrsk/traefik.rake
66
90
  homepage: https://github.com/rails/mrsk
67
91
  licenses:
68
92
  - MIT
@@ -85,5 +109,5 @@ requirements: []
85
109
  rubygems_version: 3.4.1
86
110
  signing_key:
87
111
  specification_version: 4
88
- summary: Deploy Docker containers with zero downtime to any host.
112
+ summary: Deploy Rails apps in containers to servers running Docker with zero downtime.
89
113
  test_files: []
data/lib/mrsk/engine.rb DELETED
@@ -1,4 +0,0 @@
1
- module Mrsk
2
- class Engine < ::Rails::Engine
3
- end
4
- end