mrsk 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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