mrsk 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,26 +3,42 @@ require "active_support/core_ext/string/inquiry"
3
3
  require "active_support/core_ext/module/delegation"
4
4
  require "pathname"
5
5
  require "erb"
6
+ require "mrsk/utils"
6
7
 
7
8
  class Mrsk::Configuration
8
- delegate :service, :image, :servers, :env, :labels, :registry, :builder, to: :config, allow_nil: true
9
+ delegate :service, :image, :servers, :env, :labels, :registry, :builder, to: :raw_config, allow_nil: true
10
+ delegate :argumentize, :argumentize_env_with_secrets, to: Mrsk::Utils
11
+
12
+ attr_accessor :raw_config
9
13
 
10
14
  class << self
11
- def load_file(file)
12
- if file.exist?
13
- new YAML.load(ERB.new(IO.read(file)).result).symbolize_keys
14
- else
15
- raise "Configuration file not found in #{file}"
16
- end
15
+ def create_from(base_config_file, destination: nil, version: "missing")
16
+ new(load_config_file(base_config_file).tap do |config|
17
+ if destination
18
+ config.deep_merge! \
19
+ load_config_file destination_config_file(base_config_file, destination)
20
+ end
21
+ end, version: version)
17
22
  end
18
23
 
19
- def argumentize(argument, attributes)
20
- attributes.flat_map { |k, v| [ argument, "#{k}=#{v}" ] }
21
- end
24
+ private
25
+ def load_config_file(file)
26
+ if file.exist?
27
+ YAML.load(ERB.new(IO.read(file)).result).symbolize_keys
28
+ else
29
+ raise "Configuration file not found in #{file}"
30
+ end
31
+ end
32
+
33
+ def destination_config_file(base_config_file, destination)
34
+ dir, basename = base_config_file.split
35
+ dir.join basename.to_s.remove(".yml") + ".#{destination}.yml"
36
+ end
22
37
  end
23
38
 
24
- def initialize(config, validate: true)
25
- @config = ActiveSupport::InheritableOptions.new(config)
39
+ def initialize(raw_config, version: "missing", validate: true)
40
+ @raw_config = ActiveSupport::InheritableOptions.new(raw_config)
41
+ @version = version
26
42
  ensure_required_keys_present if validate
27
43
  end
28
44
 
@@ -35,36 +51,34 @@ class Mrsk::Configuration
35
51
  roles.detect { |r| r.name == name.to_s }
36
52
  end
37
53
 
38
- def hosts
39
- hosts =
40
- case
41
- when ENV["HOSTS"]
42
- ENV["HOSTS"].split(",")
43
- when ENV["ROLES"]
44
- role_names = ENV["ROLES"].split(",")
45
- roles.select { |r| role_names.include?(r.name) }.flat_map(&:hosts)
46
- else
47
- roles.flat_map(&:hosts)
48
- end
54
+ def accessories
55
+ @accessories ||= raw_config.accessories&.keys&.collect { |name| Mrsk::Configuration::Assessory.new(name, config: self) } || []
56
+ end
49
57
 
50
- if hosts.any?
51
- hosts
52
- else
53
- raise ArgumentError, "No hosts found"
54
- end
58
+ def accessory(name)
59
+ accessories.detect { |a| a.name == name.to_s }
60
+ end
61
+
62
+
63
+ def all_hosts
64
+ roles.flat_map(&:hosts)
55
65
  end
56
66
 
57
- def primary_host
67
+ def primary_web_host
58
68
  role(:web).hosts.first
59
69
  end
60
70
 
71
+ def traefik_hosts
72
+ roles.select(&:running_traefik?).flat_map(&:hosts)
73
+ end
74
+
61
75
 
62
76
  def version
63
- @version ||= ENV["VERSION"] || `git rev-parse HEAD`.strip
77
+ @version
64
78
  end
65
79
 
66
80
  def repository
67
- [ config.registry["server"], image ].compact.join("/")
81
+ [ raw_config.registry["server"], image ].compact.join("/")
68
82
  end
69
83
 
70
84
  def absolute_image
@@ -77,15 +91,23 @@ class Mrsk::Configuration
77
91
 
78
92
 
79
93
  def env_args
80
- if config.env.present?
81
- self.class.argumentize "-e", config.env
94
+ if raw_config.env.present?
95
+ argumentize_env_with_secrets(raw_config.env)
96
+ else
97
+ []
98
+ end
99
+ end
100
+
101
+ def volume_args
102
+ if raw_config.volumes.present?
103
+ argumentize "--volume", raw_config.volumes
82
104
  else
83
105
  []
84
106
  end
85
107
  end
86
108
 
87
109
  def ssh_user
88
- config.ssh_user || "root"
110
+ raw_config.ssh_user || "root"
89
111
  end
90
112
 
91
113
  def ssh_options
@@ -93,26 +115,48 @@ class Mrsk::Configuration
93
115
  end
94
116
 
95
117
  def master_key
96
- ENV["RAILS_MASTER_KEY"] || File.read(Pathname.new(File.expand_path("config/master.key")))
118
+ unless raw_config.skip_master_key
119
+ ENV["RAILS_MASTER_KEY"] || File.read(Pathname.new(File.expand_path("config/master.key")))
120
+ end
97
121
  end
98
122
 
123
+ def to_h
124
+ {
125
+ roles: role_names,
126
+ hosts: all_hosts,
127
+ primary_host: primary_web_host,
128
+ version: version,
129
+ repository: repository,
130
+ absolute_image: absolute_image,
131
+ service_with_version: service_with_version,
132
+ env_args: env_args,
133
+ volume_args: volume_args,
134
+ ssh_options: ssh_options,
135
+ builder: raw_config.builder,
136
+ accessories: raw_config.accessories
137
+ }.compact
138
+ end
99
139
 
100
- private
101
- attr_accessor :config
102
140
 
141
+ private
103
142
  def ensure_required_keys_present
104
- %i[ service image registry ].each do |key|
105
- raise ArgumentError, "Missing required configuration for #{key}" unless config[key].present?
143
+ %i[ service image registry servers ].each do |key|
144
+ raise ArgumentError, "Missing required configuration for #{key}" unless raw_config[key].present?
106
145
  end
107
146
 
108
- %w[ username password ].each do |key|
109
- raise ArgumentError, "Missing required configuration for registry/#{key}" unless config.registry[key].present?
110
- end
147
+ if raw_config.registry["username"].blank?
148
+ raise ArgumentError, "You must specify a username for the registry in config/deploy.yml"
149
+ end
150
+
151
+ if raw_config.registry["password"].blank?
152
+ raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
153
+ end
111
154
  end
112
155
 
113
156
  def role_names
114
- config.servers.is_a?(Array) ? [ "web" ] : config.servers.keys.sort
157
+ raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
115
158
  end
116
159
  end
117
160
 
118
161
  require "mrsk/configuration/role"
162
+ require "mrsk/configuration/accessory"
@@ -0,0 +1,12 @@
1
+ require "sshkit"
2
+ require "sshkit/dsl"
3
+
4
+ class SSHKit::Backend::Abstract
5
+ def capture_with_info(*args)
6
+ capture(*args, verbosity: Logger::INFO)
7
+ end
8
+
9
+ def puts_by_host(host, output, type: "App")
10
+ puts "#{type} Host: #{host}\n#{output}\n\n"
11
+ end
12
+ end
data/lib/mrsk/utils.rb ADDED
@@ -0,0 +1,29 @@
1
+ module Mrsk::Utils
2
+ extend self
3
+
4
+ # Return a list of shell arguments using the same named argument against the passed attributes (hash or array).
5
+ def argumentize(argument, attributes, redacted: false)
6
+ Array(attributes).flat_map do |k, v|
7
+ if v.present?
8
+ [ argument, redacted ? redact("#{k}=#{v}") : "#{k}=#{v}" ]
9
+ else
10
+ [ argument, k ]
11
+ end
12
+ end
13
+ end
14
+
15
+ # Return a list of shell arguments using the same named argument against the passed attributes,
16
+ # but redacts and expands secrets.
17
+ def argumentize_env_with_secrets(env)
18
+ if (secrets = env["secret"]).present?
19
+ argumentize("-e", secrets.to_h { |key| [ key, ENV.fetch(key) ] }, redacted: true) + argumentize("-e", env["clear"])
20
+ else
21
+ argumentize "-e", env
22
+ end
23
+ end
24
+
25
+ # Copied from SSHKit::Backend::Abstract#redact to be available inside Commands classes
26
+ def redact(arg) # Used in execute_command to hide redact() args a user passes in
27
+ arg.to_s.extend(SSHKit::Redaction) # to_s due to our inability to extend Integer, etc
28
+ end
29
+ end
data/lib/mrsk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mrsk
2
- VERSION = "0.1.0"
2
+ VERSION = "0.3.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.1.0
4
+ version: 0.3.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-14 00:00:00.000000000 Z
11
+ date: 2023-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -64,6 +64,7 @@ files:
64
64
  - bin/mrsk
65
65
  - lib/mrsk.rb
66
66
  - lib/mrsk/cli.rb
67
+ - lib/mrsk/cli/accessory.rb
67
68
  - lib/mrsk/cli/app.rb
68
69
  - lib/mrsk/cli/base.rb
69
70
  - lib/mrsk/cli/build.rb
@@ -75,17 +76,23 @@ files:
75
76
  - lib/mrsk/cli/traefik.rb
76
77
  - lib/mrsk/commander.rb
77
78
  - lib/mrsk/commands.rb
79
+ - lib/mrsk/commands/accessory.rb
78
80
  - lib/mrsk/commands/app.rb
79
81
  - lib/mrsk/commands/base.rb
80
82
  - lib/mrsk/commands/builder.rb
83
+ - lib/mrsk/commands/builder/base.rb
81
84
  - lib/mrsk/commands/builder/multiarch.rb
82
85
  - lib/mrsk/commands/builder/multiarch/remote.rb
83
86
  - lib/mrsk/commands/builder/native.rb
87
+ - lib/mrsk/commands/builder/native/remote.rb
84
88
  - lib/mrsk/commands/prune.rb
85
89
  - lib/mrsk/commands/registry.rb
86
90
  - lib/mrsk/commands/traefik.rb
87
91
  - lib/mrsk/configuration.rb
92
+ - lib/mrsk/configuration/accessory.rb
88
93
  - lib/mrsk/configuration/role.rb
94
+ - lib/mrsk/sshkit_with_ext.rb
95
+ - lib/mrsk/utils.rb
89
96
  - lib/mrsk/version.rb
90
97
  homepage: https://github.com/rails/mrsk
91
98
  licenses:
@@ -106,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
113
  - !ruby/object:Gem::Version
107
114
  version: '0'
108
115
  requirements: []
109
- rubygems_version: 3.4.1
116
+ rubygems_version: 3.4.5
110
117
  signing_key:
111
118
  specification_version: 4
112
119
  summary: Deploy Rails apps in containers to servers running Docker with zero downtime.