minio_runner 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fee26785f28262a4b0f39f5020886edcddd996ca6040e5b1e952a9583199aa36
4
+ data.tar.gz: 19ba0e2e0a070b3caf5fbfc1ba04e4a5cca9a285e80b224626e112cbd45c0202
5
+ SHA512:
6
+ metadata.gz: 57fc047cd89912f6ad31b2fc5c349d74182c44fdc9bf6ef2e5cc5beb613eff56d99ff04914761047c0deb944bb433e581e8f0017000fdc8309a0a3af1628f1bd
7
+ data.tar.gz: 5a16f8c6d73deec031690b3bdb21d602d6e162ab22a8b56f4ab6abd874bfe0aa039bb4c31a1de79245c0116cabc7118d58ded7d9b499f1f8b018beabdd633aeb
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in minio_runner.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "minitest", "~> 5.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,93 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ minio_runner (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ansi (1.5.0)
10
+ ast (2.4.2)
11
+ builder (3.2.4)
12
+ byebug (11.1.3)
13
+ coderay (1.1.3)
14
+ json (2.6.3)
15
+ language_server-protocol (3.17.0.3)
16
+ method_source (1.0.0)
17
+ minitest (5.19.0)
18
+ minitest-color (0.0.2)
19
+ minitest (~> 5)
20
+ minitest-line (0.6.5)
21
+ minitest (~> 5.0)
22
+ minitest-reporters (1.6.1)
23
+ ansi
24
+ builder
25
+ minitest (>= 5.0)
26
+ ruby-progressbar
27
+ parallel (1.23.0)
28
+ parser (3.2.2.3)
29
+ ast (~> 2.4.1)
30
+ racc
31
+ prettier_print (1.2.1)
32
+ pry (0.14.2)
33
+ coderay (~> 1.1)
34
+ method_source (~> 1.0)
35
+ pry-byebug (3.10.1)
36
+ byebug (~> 11.0)
37
+ pry (>= 0.13, < 0.15)
38
+ racc (1.7.1)
39
+ rainbow (3.1.1)
40
+ rake (13.0.6)
41
+ regexp_parser (2.8.1)
42
+ rexml (3.2.6)
43
+ rubocop (1.55.1)
44
+ json (~> 2.3)
45
+ language_server-protocol (>= 3.17.0)
46
+ parallel (~> 1.10)
47
+ parser (>= 3.2.2.3)
48
+ rainbow (>= 2.2.2, < 4.0)
49
+ regexp_parser (>= 1.8, < 3.0)
50
+ rexml (>= 3.2.5, < 4.0)
51
+ rubocop-ast (>= 1.28.1, < 2.0)
52
+ ruby-progressbar (~> 1.7)
53
+ unicode-display_width (>= 2.4.0, < 3.0)
54
+ rubocop-ast (1.29.0)
55
+ parser (>= 3.2.1.0)
56
+ rubocop-capybara (2.18.0)
57
+ rubocop (~> 1.41)
58
+ rubocop-discourse (3.3.0)
59
+ rubocop (>= 1.1.0)
60
+ rubocop-rspec (>= 2.0.0)
61
+ rubocop-factory_bot (2.23.1)
62
+ rubocop (~> 1.33)
63
+ rubocop-rspec (2.23.0)
64
+ rubocop (~> 1.33)
65
+ rubocop-capybara (~> 2.17)
66
+ rubocop-factory_bot (~> 2.22)
67
+ ruby-progressbar (1.13.0)
68
+ spy (1.0.5)
69
+ syntax_tree (6.1.1)
70
+ prettier_print (>= 1.2.0)
71
+ syntax_tree-disable_ternary (1.0.0)
72
+ unicode-display_width (2.4.2)
73
+
74
+ PLATFORMS
75
+ arm64-darwin-21
76
+ x86_64-linux
77
+
78
+ DEPENDENCIES
79
+ minio_runner!
80
+ minitest (~> 5.0)
81
+ minitest-color
82
+ minitest-line
83
+ minitest-reporters
84
+ pry
85
+ pry-byebug
86
+ rake (~> 13.0)
87
+ rubocop-discourse
88
+ spy
89
+ syntax_tree
90
+ syntax_tree-disable_ternary
91
+
92
+ BUNDLED WITH
93
+ 2.4.13
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Martin Brennan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # MinioRunner
2
+
3
+ Manages the installed [minio](https://min.io/) binary and handles setup and
4
+ teardown of the minio server.
5
+
6
+ ## Description
7
+
8
+ This is a simple way of managing the locally installed minio server using an
9
+ installed binary. This can be used to start/stop the server when running
10
+ automated tests or developing locally against an S3 alternative. It also
11
+ installs the [`mc` (minio client)](https://min.io/docs/minio/linux/reference/minio-mc.html) CLI
12
+ tool to make interacting with the server easier.
13
+
14
+ This an extremely focused gem and will not focus on all possible different
15
+ configurations and binaries of minio. Only Linux and macOS platforms are
16
+ supported at this time.
17
+
18
+ This gem was inspired by the [webdrivers](https://github.com/titusfortner/webdrivers)
19
+ project.
20
+
21
+ ## Usage
22
+
23
+ In your Gemfile:
24
+
25
+ ```ruby
26
+ gem 'minio_runner', require: false
27
+ ```
28
+
29
+ In your project:
30
+
31
+ ```ruby
32
+ require 'minio_runner'
33
+ ```
34
+
35
+ The minio runner will not automatically locate, download, and start minio. You
36
+ will need to use the following calls; for example in your before/after suite
37
+ setup and teardown for rspec.
38
+
39
+ ```ruby
40
+ # Locate and download the minio binary if it does not exist, and start the server with provided configuration.
41
+ # The binary will be updated if the new version (which is checked every time `MinioRunner.cache_time` expires)
42
+ # is greater than the installed version.
43
+ MinioRunner.start
44
+
45
+ # Stop the currently running server.
46
+ MinioRunner.stop
47
+ ```
48
+
49
+ ### Download Location
50
+
51
+ The default download location is `~/.minio_runner` directory, and this is configurable:
52
+
53
+ ```ruby
54
+ MinioRunner.config.install_dir = '/minio_runner/install/dir'
55
+ ```
56
+
57
+ Alternatively, you can define the path via the `MINIO_RUNNER_INSTALL_DIR` environment variable.
58
+ The environment variable will take precedence.
59
+
60
+ ### Caching minio version
61
+
62
+ You can set Minio Runner to only look for updates if the previous check
63
+ was longer ago than a specified number of seconds.
64
+
65
+ ```ruby
66
+ MinioRunner.config.cache_time = 86_400 # Default: 86,400 Seconds (24 hours)
67
+ ```
68
+
69
+ Alternatively, you can define the time via the `MINIO_RUNNER_CACHE_TIME` environment variable.
70
+ The environment variable will take precedence.
71
+
72
+ ### Rake tasks
73
+
74
+ You can run `bundle exec rake -T -a` to see all the rake tasks. The ones specifically related to
75
+ minio runner will be namespaced into minio_runner.
76
+
77
+ ### Logging
78
+
79
+ The logging level can be configured for debugging purpose via the `MINIO_RUNNER_LOG_LEVEL` environment variable.
80
+
81
+ The available values are found in https://ruby-doc.org/stdlib-2.4.0/libdoc/logger/rdoc/Logger/Severity.html.
82
+
83
+ The minio server will log to the `install_dir` in a `minio.log` file.
84
+
85
+ ## Minio configuration
86
+
87
+ Only a small subset of minio configuration (defined at https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#environment-variables)
88
+ is supported. The subset of configuration options can be found from running the `list_configurable_env`
89
+ rake task.
90
+
91
+ All minio configuration can also be specified via `MinioRunner.config`, and anything
92
+ set in this way will override environment variables. Environment variables should
93
+ be in the format `MINIO_RUNNER_MINIO_X`:
94
+
95
+ ```ruby
96
+ MinioRunner.config do |config|
97
+ config.minio_port = 9000 # MINIO_RUNNER_MINIO_PORT
98
+ config.minio_console_address = 9001 # MINIO_RUNNER_MINO_CONSOLE_ADDRESS
99
+ config.minio_domain = 'minio.local' # MINIO_RUNNER_MINIO_DOMAIN
100
+ end
101
+ ```
102
+
103
+ The configuration in ruby will use the exact same names as the environment
104
+ variables for minio.
105
+
106
+ ### Aliases
107
+
108
+ By default a `local` alias is automatically created via the `mc` tool, which will point
109
+ to `localhost` at the configured `MINIO_RUNNER_MINIO_PORT`. No other aliases are supported
110
+ at this time.
111
+
112
+ ### Buckets
113
+
114
+ You can specify the buckets that will be created (if they do not exist) when the minio server
115
+ starts using the `MinioRunner.config` call above or using the `MINIO_RUNNER_BUCKETS` environment
116
+ variable with a comma-separated list. Only S3-compatible buckets will be made.
117
+
118
+ ```ruby
119
+ MinioRunner.config.buckets = ["testbucket", "media"]
120
+
121
+ # MINIO_RUNNER_BUCKETS="testbucket,media"
122
+ ```
123
+
124
+ Buckets will be made public to anonymous users if they are specified in the `public_buckets` configuration,
125
+ which can also be set with the `MINIO_RUNNER_PUBLIC_BUCKETS` environment variable.
126
+
127
+ ### Hosts file
128
+
129
+ **An important step** that you must manually do yourself is to modify your `/etc/hosts` file to add an
130
+ entry for your minio server defined by `MINIO_RUNNER_MINIO_DOMAIN` and also for any bucket defined
131
+ via `MINIO_RUNNER_BUCKETS`, since they will be used as virtual-host style buckets.
132
+
133
+ For example:
134
+
135
+ ```
136
+ 127.0.0.1 minio.local
137
+ 127.0.0.1 testbucket.minio.local
138
+ ```
139
+
140
+ For macOS, there are some issues which cause large delays for .local domain names. See
141
+ https://superuser.com/a/1297335/245469 and https://stackoverflow.com/a/17982964/875941. To
142
+ resolve this, you need to add IPV6 lookup addresses to the hosts file, and it helps to put
143
+ all the entries on one line.
144
+
145
+ ```
146
+ ::1 minio.local testbucket.minio.local
147
+ fe80::1%lo0 minio.local testbucket.minio.local
148
+ 127.0.0.1 minio.local testbucket.minio.local
149
+ ```
150
+
151
+ ## Development
152
+
153
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
154
+
155
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
156
+
157
+ ## Contributing
158
+
159
+ Bug reports and pull requests are welcome on GitHub at https://github.com/discourse/minio_runner.
160
+
161
+ ## License
162
+
163
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+ require "bundler/setup"
6
+ require "minio_runner"
7
+ require "pry"
8
+
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << "test"
11
+ t.libs << "lib"
12
+ t.test_files = FileList["test/**/test_*.rb"]
13
+ end
14
+
15
+ task default: :test
16
+
17
+ namespace :minio_runner do
18
+ desc "Completely removes the minio runner install directory which includes binaries and version files"
19
+ task :remove do
20
+ MinioRunner.remove_install_dir
21
+ end
22
+
23
+ desc "Installs the minio and mc binaries"
24
+ task :install do
25
+ MinioRunner.install_binaries
26
+ end
27
+
28
+ desc "Forces an update of the binaries. Equivalent to running the remove then install tasks."
29
+ task :update do
30
+ MinioRunner.remove_install_dir
31
+ MinioRunner.install_binaries
32
+ end
33
+
34
+ desc "Installs binaries, starts the minio server, and makes sure it is set up to receive requests. Stops server on exit."
35
+ task :start do
36
+ MinioRunner.start
37
+ MinioRunner.logger.info("Minio server is now started. Press any key to stop.")
38
+ STDIN.gets.strip
39
+ end
40
+
41
+ desc "Lists all environment varibales that can be configured for MinioRunner"
42
+ task :list_configurable_env do
43
+ puts "These are the configurable environment variables for MinioRunner:"
44
+ puts "-" * 20
45
+
46
+ MinioRunner::Config::CONFIGURABLE_ENV_VARS
47
+ .sort_by { |key| key }
48
+ .each do |key, value|
49
+ printf "%-40s %s\n", "#{MinioRunner::System::ENV_VAR_PREFIX}#{key.upcase}", value
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "system"
4
+
5
+ module MinioRunner
6
+ class BaseBinary
7
+ class << self
8
+ def sha_file_name
9
+ "#{name}.sha256sum"
10
+ end
11
+
12
+ def version_file_name
13
+ "#{name}.version"
14
+ end
15
+
16
+ def version_file_path
17
+ File.join(MinioRunner.config.install_dir, version_file_name)
18
+ end
19
+
20
+ def checksum_file_path
21
+ File.join(MinioRunner.config.install_dir, sha_file_name)
22
+ end
23
+
24
+ def binary_file_path
25
+ File.join(MinioRunner.config.install_dir, name)
26
+ end
27
+
28
+ def platform_binary_url
29
+ "#{platform_base_url}#{name}"
30
+ end
31
+
32
+ def platform_sha256sum_url
33
+ "#{platform_binary_url}.sha256sum"
34
+ end
35
+
36
+ def name
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def base_url
41
+ raise NotImplementedError
42
+ end
43
+
44
+ def platform_base_url
45
+ raise NotImplementedError
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "network"
4
+
5
+ module MinioRunner
6
+ class BinaryManager
7
+ class << self
8
+ def install(binary)
9
+ new(binary).install
10
+ end
11
+ end
12
+
13
+ attr_reader :binary
14
+
15
+ def initialize(binary)
16
+ @binary = binary
17
+ end
18
+
19
+ def install
20
+ if installed?
21
+ if version_cache_expired? && new_version_available?
22
+ MinioRunner.logger.debug("New version of #{binary.name} available. Downloading...")
23
+ download_binary
24
+ else
25
+ MinioRunner.logger.debug("Version for #{binary.name} is up to date.")
26
+ end
27
+ else
28
+ MinioRunner.logger.debug("#{binary.name} not installed. Downloading...")
29
+ download_binary
30
+ end
31
+ MinioRunner.logger.debug("#{binary.name} installed successfully!")
32
+ end
33
+
34
+ def new_version_available?
35
+ old_version = File.read(binary.version_file_path)
36
+ new_version = nil
37
+
38
+ Network.download(binary.platform_sha256sum_url) do |sha_file|
39
+ new_version = File.read(sha_file.path)
40
+ FileUtils.cp(sha_file, File.join(MinioRunner.config.install_dir, binary.sha_file_name))
41
+ FileUtils.cp(sha_file, File.join(MinioRunner.config.install_dir, binary.version_file_name))
42
+ end
43
+
44
+ old_version != new_version
45
+ end
46
+
47
+ def download_binary
48
+ Network.download(binary.platform_binary_url) do |binary_file|
49
+ FileUtils.cp(binary_file, File.join(MinioRunner.config.install_dir, binary.name))
50
+ end
51
+
52
+ Network.download(binary.platform_sha256sum_url) do |sha_file|
53
+ FileUtils.cp(sha_file, File.join(MinioRunner.config.install_dir, binary.sha_file_name))
54
+ FileUtils.cp(sha_file, File.join(MinioRunner.config.install_dir, binary.version_file_name))
55
+ end
56
+
57
+ FileUtils.chmod("ugo+rx", binary.binary_file_path)
58
+ end
59
+
60
+ def installed?
61
+ File.exist?(binary.binary_file_path) && File.exist?(binary.version_file_path)
62
+ end
63
+
64
+ def version_cache_expired?
65
+ Time.now - File.mtime(binary.version_file_path) > MinioRunner.config.cache_time
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copied with modification from https://github.com/SeleniumHQ/selenium/
4
+
5
+ # Licensed to the Software Freedom Conservancy (SFC) under one
6
+ # or more contributor license agreements. See the NOTICE file
7
+ # distributed with this work for additional information
8
+ # regarding copyright ownership. The SFC licenses this file
9
+ # to you under the Apache License, Version 2.0 (the
10
+ # "License"); you may not use this file except in compliance
11
+ # with the License. You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing,
16
+ # software distributed under the License is distributed on an
17
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
+ # KIND, either express or implied. See the License for the
19
+ # specific language governing permissions and limitations
20
+ # under the License.
21
+
22
+ module MinioRunner
23
+ #
24
+ # @api private
25
+ #
26
+
27
+ class ChildProcess
28
+ TimeoutError = Class.new(StandardError)
29
+
30
+ SIGTERM = "TERM"
31
+ SIGKILL = "KILL"
32
+
33
+ POLL_INTERVAL = 0.1
34
+
35
+ attr_accessor :detach
36
+ attr_writer :io
37
+ attr_reader :pid
38
+
39
+ def self.build(command, env: {}, log_file: File::NULL)
40
+ new(command, env: env, log_file: log_file)
41
+ end
42
+
43
+ def initialize(command, env: {}, log_file: File::NULL)
44
+ @command = command
45
+ @detach = false
46
+ @pid = nil
47
+ @status = nil
48
+ @env = env
49
+ @log_file = log_file
50
+ end
51
+
52
+ def io
53
+ @io ||= File::NULL
54
+ end
55
+
56
+ def start
57
+ options = { %i[out err] => @log_file }
58
+
59
+ # TODO (martin) Maybe don't log ENV here?
60
+ MinioRunner.logger.debug("Starting process: #{@command} with #{options} and ENV #{@env}")
61
+ @pid = Process.spawn(@env, @command.join(" "), options)
62
+ MinioRunner.logger.debug(" -> pid: #{@pid}")
63
+
64
+ Process.detach(@pid) if detach
65
+ end
66
+
67
+ def stop(timeout = 3)
68
+ return unless @pid
69
+ return if exited?
70
+
71
+ MinioRunner.logger.debug("Sending TERM to process: #{@pid}")
72
+ terminate(@pid)
73
+ poll_for_exit(timeout)
74
+
75
+ MinioRunner.logger.debug(" -> stopped #{@pid}")
76
+ rescue TimeoutError, Errno::EINVAL
77
+ MinioRunner.logger.debug(" -> sending KILL to process: #{@pid}")
78
+ kill(@pid)
79
+ wait
80
+ MinioRunner.logger.debug(" -> killed #{@pid}")
81
+ end
82
+
83
+ def alive?
84
+ @pid && !exited?
85
+ end
86
+
87
+ def exited?
88
+ return unless @pid
89
+
90
+ MinioRunner.logger.debug("Checking if #{@pid} is exited:")
91
+ _, @status = Process.waitpid2(@pid, Process::WNOHANG | Process::WUNTRACED) if @status.nil?
92
+ return if @status.nil?
93
+
94
+ exit_code = @status.exitstatus || @status.termsig
95
+ MinioRunner.logger.debug(" -> exit code is #{exit_code.inspect}")
96
+
97
+ !!exit_code
98
+ end
99
+
100
+ def poll_for_exit(timeout)
101
+ MinioRunner.logger.debug("Polling #{timeout} seconds for exit of #{@pid}")
102
+
103
+ end_time = Time.now + timeout
104
+ sleep POLL_INTERVAL until exited? || Time.now > end_time
105
+
106
+ raise TimeoutError, " -> #{@pid} still alive after #{timeout} seconds" unless exited?
107
+ end
108
+
109
+ def wait
110
+ return if exited?
111
+
112
+ _, @status = Process.waitpid2(@pid)
113
+ end
114
+
115
+ private
116
+
117
+ def terminate(pid)
118
+ Process.kill(SIGTERM, pid)
119
+ end
120
+
121
+ def kill(pid)
122
+ Process.kill(SIGKILL, pid)
123
+ rescue Errno::ECHILD, Errno::ESRCH
124
+ # already dead
125
+ end
126
+ end # ChildProcess
127
+ end # MinioRunner
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MinioRunner
4
+ class Config
5
+ attr_accessor :install_dir, :cache_time, :buckets, :public_buckets, :log_level
6
+ attr_accessor :minio_data_directory,
7
+ :minio_root_user,
8
+ :minio_root_password,
9
+ :minio_domain,
10
+ :minio_port,
11
+ :minio_console_port
12
+
13
+ DEFAULT_INSTALL_DIR = "~/.minio_runner"
14
+ DEFAULT_CACHE_TIME = 86_400 # 24 hours in seconds
15
+
16
+ DEFAULT_MINIO_SERVER_DATA_DIR = "~/.minio_runner/data"
17
+ DEFAULT_MINIO_PORT = 9000
18
+ DEFAULT_MINIO_CONSOLE_PORT = 9001
19
+ DEFAULT_MINIO_ROOT_USER = "minioadmin"
20
+ DEFAULT_MINIO_ROOT_PASSWORD = "minioadmin"
21
+
22
+ CONFIGURABLE_ENV_VARS = {
23
+ install_dir: "Path to install minio_runner (default #{DEFAULT_INSTALL_DIR})",
24
+ log_level:
25
+ "Log level for minio_runner (DEBUG, INFO, WARN, ERROR, FATAL, UNKNOWN, default INFO)",
26
+ cache_time:
27
+ "Time in seconds to cache minio_runner downloads for both the minio server and mc binaries (default #{DEFAULT_CACHE_TIME} seconds)",
28
+ buckets: "List of buckets to create on startup, comma separated.",
29
+ public_buckets: "List of buckets to make public for anonymous users, comma separated.",
30
+ minio_domain: "Domain to use for minio server (default localhost)",
31
+ minio_data_directory:
32
+ "Path to minio server data directory (default #{DEFAULT_MINIO_SERVER_DATA_DIR})",
33
+ minio_root_user: "User for minio server root user (default #{DEFAULT_MINIO_ROOT_USER})",
34
+ minio_root_password:
35
+ "Password for minio server root user (default #{DEFAULT_MINIO_ROOT_PASSWORD})",
36
+ minio_console_port: "Port for minio server console (default #{DEFAULT_MINIO_CONSOLE_PORT})",
37
+ minio_port: "Port for minio server (default #{DEFAULT_MINIO_PORT})",
38
+ }
39
+
40
+ def initialize
41
+ self.install_dir = System.env(:install_dir) || DEFAULT_INSTALL_DIR
42
+ self.cache_time = System.env(:cache_time) || DEFAULT_CACHE_TIME
43
+ self.buckets = System.env(:buckets)&.split(",") || []
44
+ self.public_buckets = System.env(:public_buckets)&.split(",") || []
45
+
46
+ # minio server configuration
47
+ self.minio_data_directory = System.env(:minio_data_directory) || DEFAULT_MINIO_SERVER_DATA_DIR
48
+ self.minio_root_user = System.env(:minio_root_user) || DEFAULT_MINIO_ROOT_USER
49
+ self.minio_root_password = System.env(:minio_root_password) || DEFAULT_MINIO_ROOT_PASSWORD
50
+ self.minio_domain = System.env(:minio_domain) || "localhost"
51
+ self.minio_port = System.env(:minio_port) || DEFAULT_MINIO_PORT
52
+ self.minio_console_port = System.env(:minio_console_port) || DEFAULT_MINIO_CONSOLE_PORT
53
+ end
54
+
55
+ def install_dir=(dir)
56
+ @install_dir = File.expand_path(dir)
57
+ end
58
+
59
+ def minio_data_directory=(dir)
60
+ @minio_data_directory = File.expand_path(dir)
61
+ end
62
+
63
+ def minio_server_url
64
+ "http://#{minio_domain}:#{minio_port}"
65
+ end
66
+
67
+ def minio_urls
68
+ urls = [minio_server_url]
69
+ buckets.each { |bucket| urls << "http://#{bucket}.#{minio_domain}:#{minio_port}" }
70
+ urls
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_binary"
4
+
5
+ module MinioRunner
6
+ class McBinary < BaseBinary
7
+ class << self
8
+ def name
9
+ "mc"
10
+ end
11
+
12
+ def base_url
13
+ "https://dl.min.io/client/mc/release"
14
+ end
15
+
16
+ def platform_base_url
17
+ if System.linux?
18
+ "#{base_url}/linux-amd64/"
19
+ elsif System.mac?
20
+ System.mac_m? ? "#{base_url}/darwin-arm64/" : "#{base_url}/darwin-amd64/"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MinioRunner
4
+ class McManager
5
+ class << self
6
+ def command
7
+ ["#{MinioRunner::McBinary.binary_file_path}"]
8
+ end
9
+
10
+ def bucket_exists?(alias_name, name)
11
+ system(*command.concat(["ls", "#{alias_name}/#{name}"]))
12
+ end
13
+
14
+ def create_bucket(alias_name, name)
15
+ MinioRunner.logger.debug("Creating bucket #{alias_name}/#{name}...")
16
+ if !bucket_exists?(alias_name, name)
17
+ system(*command.concat(["mb", "#{alias_name}/#{name}"]))
18
+ MinioRunner.logger.debug("Created #{alias_name}/#{name}.")
19
+ else
20
+ MinioRunner.logger.debug("Bucket #{alias_name}/#{name} already exists, doing nothing.")
21
+ end
22
+ end
23
+
24
+ def set_alias(name, url)
25
+ MinioRunner.logger.debug("Setting alias #{name} to #{url}...")
26
+ system(
27
+ *command.concat(
28
+ [
29
+ "alias",
30
+ "set",
31
+ name,
32
+ url,
33
+ MinioRunner.config.minio_root_user,
34
+ MinioRunner.config.minio_root_password,
35
+ ],
36
+ ),
37
+ )
38
+ MinioRunner.logger.debug("Set alias #{name} to #{url}.")
39
+ end
40
+
41
+ def set_anon(alias_name, bucket, policy)
42
+ MinioRunner.logger.debug(
43
+ "Setting anonymous access for #{alias_name}/#{bucket} to policy #{policy}...",
44
+ )
45
+ system(*command.concat(["anonymous", "set", policy, "#{alias_name}/#{bucket}"]))
46
+ MinioRunner.logger.debug("Anonymous access set for #{alias_name}/#{bucket}.")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_binary"
4
+
5
+ module MinioRunner
6
+ class MinioBinary < BaseBinary
7
+ class << self
8
+ def name
9
+ "minio"
10
+ end
11
+
12
+ def base_url
13
+ "https://dl.min.io/server/minio/release"
14
+ end
15
+
16
+ def platform_binary_url
17
+ "#{platform_base_url}#{name}"
18
+ end
19
+
20
+ def platform_sha256sum_url
21
+ "#{platform_binary_url}.sha256sum"
22
+ end
23
+
24
+ def platform_base_url
25
+ if System.linux?
26
+ "#{base_url}/linux-amd64/"
27
+ elsif System.mac?
28
+ System.mac_m? ? "#{base_url}/darwin-arm64/" : "#{base_url}/darwin-amd64/"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "child_process"
4
+
5
+ module MinioRunner
6
+ class MinioServerManager
7
+ SERVER_STOP_TIMEOUT_SECONDS = 5
8
+
9
+ attr_reader :pid, :process
10
+
11
+ class << self
12
+ def start
13
+ @server = new
14
+ @server.start
15
+ end
16
+
17
+ def stop
18
+ return if @server.nil?
19
+ @server.stop
20
+ end
21
+ end
22
+
23
+ def start
24
+ if process_running?
25
+ MinioRunner.logger.debug("Already started minio server.")
26
+ return
27
+ end
28
+
29
+ MinioRunner.logger.debug("Starting minio server...")
30
+
31
+ MinioRunner::System.exit_hook { stop }
32
+
33
+ @process =
34
+ MinioRunner::ChildProcess.build(
35
+ server_command,
36
+ env: {
37
+ "MINIO_ROOT_USER" => MinioRunner.config.minio_root_user,
38
+ "MINIO_ROOT_PASSWORD" => MinioRunner.config.minio_root_password,
39
+ "MINIO_DOMAIN" => MinioRunner.config.minio_domain,
40
+ },
41
+ log_file: log_file_path,
42
+ )
43
+
44
+ @process.start
45
+
46
+ # Make sure the minio server is ready to accept requests.
47
+ health_check(retries: 3)
48
+
49
+ MinioRunner.logger.debug("minio server running at pid #{@process.pid}!")
50
+ end
51
+
52
+ def stop
53
+ return if process_exited?
54
+
55
+ MinioRunner.logger.debug("Stopping minio server running at pid #{@pid}...")
56
+ @process.stop(SERVER_STOP_TIMEOUT_SECONDS)
57
+ @process = nil
58
+ MinioRunner.logger.debug("minio server stopped")
59
+ end
60
+
61
+ def process_running?
62
+ defined?(@process) && @process&.alive?
63
+ end
64
+
65
+ def process_exited?
66
+ @process.nil? || @process.exited?
67
+ end
68
+
69
+ private
70
+
71
+ def server_command
72
+ command = []
73
+
74
+ # server start command for minio
75
+ command << "#{MinioRunner::MinioBinary.binary_file_path} server #{MinioRunner.config.minio_data_directory}"
76
+
77
+ # flags for minio
78
+ command << "--console-address :#{MinioRunner.config.minio_console_port}"
79
+ command << "--address #{MinioRunner.config.minio_domain}:#{MinioRunner.config.minio_port}"
80
+
81
+ command
82
+ end
83
+
84
+ def log_file_path
85
+ "#{MinioRunner.config.install_dir}/minio.log"
86
+ end
87
+
88
+ def health_check(retries:)
89
+ begin
90
+ Network.get("#{MinioRunner.config.minio_server_url}/minio/health/live")
91
+ rescue StandardError
92
+ if retries.positive?
93
+ sleep 1
94
+ health_check(retries: retries - 1)
95
+ else
96
+ raise "Minio server failed to start."
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "tempfile"
5
+
6
+ module MinioRunner
7
+ class Network
8
+ class << self
9
+ def get(url)
10
+ MinioRunner.logger.debug("Making network call to #{url}")
11
+
12
+ begin
13
+ response = Net::HTTP.get_response(URI(url))
14
+ rescue SocketError
15
+ raise "Can not reach #{url}"
16
+ end
17
+
18
+ MinioRunner.logger.debug("Get response: #{response.inspect}")
19
+
20
+ case response
21
+ when Net::HTTPSuccess
22
+ response.body
23
+ else
24
+ raise "#{response.class::EXCEPTION_TYPE}: #{response.code} \"#{response.message}\" with #{url}"
25
+ end
26
+ end
27
+
28
+ def download(url, &block)
29
+ file_name = File.basename(url)
30
+ tempfile =
31
+ Tempfile.open(["", file_name], binmode: true) do |file|
32
+ file.print Network.get(url)
33
+ file
34
+ end
35
+
36
+ raise "Could not download #{url}" unless File.exist?(tempfile.to_path)
37
+
38
+ MinioRunner.logger.debug("Successfully downloaded #{tempfile.to_path}")
39
+
40
+ yield tempfile if block_given?
41
+ ensure
42
+ tempfile&.close!
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module MinioRunner
6
+ class System
7
+ class InvalidEnvVar < StandardError
8
+ end
9
+ class InvalidPlatform < StandardError
10
+ end
11
+
12
+ ENV_VAR_PREFIX = "MINIO_RUNNER_"
13
+
14
+ class << self
15
+ def env(name)
16
+ name = name.to_sym
17
+
18
+ if !defined_env?(name)
19
+ raise MinioRunner::System::InvalidEnvVar.new(
20
+ "Environment variable #{ENV_VAR_PREFIX}#{name.upcase} is not valid for minio_runner.",
21
+ )
22
+ end
23
+
24
+ ENV["#{ENV_VAR_PREFIX}#{name.upcase}"]
25
+ end
26
+
27
+ def defined_env?(name)
28
+ MinioRunner::Config::CONFIGURABLE_ENV_VARS.keys.include?(name)
29
+ end
30
+
31
+ def make_install_dir
32
+ if !Dir.exist?(MinioRunner.config.install_dir)
33
+ MinioRunner.logger.debug("Making install directory #{MinioRunner.config.install_dir}.")
34
+ FileUtils.mkdir_p(MinioRunner.config.install_dir)
35
+ end
36
+ end
37
+
38
+ def valid_platform?
39
+ mac? || linux?
40
+ end
41
+
42
+ def validate_platform
43
+ if !valid_platform?
44
+ raise MinioRunner::System::InvalidPlatform.new(
45
+ "MinioRunner only supports Mac, macOS and Linux.",
46
+ )
47
+ end
48
+ end
49
+
50
+ def mac?
51
+ Gem::Platform.local.os[/darwin|mac os/]
52
+ end
53
+
54
+ def mac_m?
55
+ mac? && Gem::Platform.local.cpu === "arm64"
56
+ end
57
+
58
+ def linux?
59
+ Gem::Platform.local.os[/linux/]
60
+ end
61
+
62
+ def exit_hook
63
+ pid = Process.pid
64
+ at_exit { yield if Process.pid == pid }
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MinioRunner
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require_relative "minio_runner/version"
5
+ require_relative "minio_runner/system"
6
+ require_relative "minio_runner/config"
7
+ require_relative "minio_runner/minio_binary"
8
+ require_relative "minio_runner/mc_binary"
9
+ require_relative "minio_runner/binary_manager"
10
+ require_relative "minio_runner/minio_server_manager"
11
+ require_relative "minio_runner/mc_manager"
12
+
13
+ module MinioRunner
14
+ class << self
15
+ def config(&block)
16
+ @config ||= MinioRunner::Config.new
17
+ if block_given?
18
+ yield @config
19
+ else
20
+ @config
21
+ end
22
+ end
23
+
24
+ def logger
25
+ @logger ||=
26
+ Logger
27
+ .new(STDOUT)
28
+ .tap do |logger|
29
+ logger.level =
30
+ (
31
+ if System.env(:log_level)
32
+ Kernel.const_get("Logger::#{System.env(:log_level)}")
33
+ else
34
+ Logger::INFO
35
+ end
36
+ )
37
+
38
+ original_formatter = logger.formatter || Logger::Formatter.new
39
+ logger.formatter =
40
+ proc do |severity, time, progname, msg|
41
+ original_formatter.call(
42
+ severity,
43
+ time,
44
+ progname,
45
+ "[MinioRunner]: #{msg.strip.dump}",
46
+ )
47
+ end
48
+ end
49
+ end
50
+
51
+ def start
52
+ logger.debug("Starting minio_runner...")
53
+
54
+ install_binaries
55
+ start_server
56
+ setup_alias
57
+ setup_buckets
58
+
59
+ logger.debug("Started minio_runner.")
60
+ end
61
+
62
+ def install_binaries
63
+ System.validate_platform
64
+ System.make_install_dir
65
+ MinioRunner::BinaryManager.install(MinioRunner::McBinary)
66
+ MinioRunner::BinaryManager.install(MinioRunner::MinioBinary)
67
+ end
68
+
69
+ def start_server
70
+ MinioRunner::MinioServerManager.start
71
+ end
72
+
73
+ def setup_alias
74
+ MinioRunner::McManager.set_alias("local", "http://localhost:#{MinioRunner.config.minio_port}")
75
+ end
76
+
77
+ def setup_buckets
78
+ MinioRunner.config.buckets.each do |bucket|
79
+ MinioRunner::McManager.create_bucket("local", bucket)
80
+ end
81
+ MinioRunner.config.public_buckets.each do |bucket|
82
+ MinioRunner::McManager.set_anon("local", bucket, "public")
83
+ end
84
+ end
85
+
86
+ def stop
87
+ logger.debug("Stopping minio_runner...")
88
+ MinioRunner::MinioServerManager.stop
89
+ logger.debug("Stopped minio_runner.")
90
+ end
91
+
92
+ def reset_config!
93
+ @config = nil
94
+ end
95
+
96
+ def remove_install_dir
97
+ logger.info("Removing MinioRunner install directory at #{MinioRunner.config.install_dir}...")
98
+ FileUtils.rm_rf(MinioRunner.config.install_dir) if Dir.exist?(MinioRunner.config.install_dir)
99
+ logger.info("Done removing MinioRunner install directory.")
100
+ end
101
+ end
102
+ end
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minio_runner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Martin Brennan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: syntax_tree
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: syntax_tree-disable_ternary
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop-discourse
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-color
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-reporters
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest-line
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: spy
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry-byebug
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description: Manages local minio binary installs and handles stopping and starting
140
+ minio server
141
+ email: martin@discourse.org
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - Gemfile
147
+ - Gemfile.lock
148
+ - LICENSE.txt
149
+ - README.md
150
+ - Rakefile
151
+ - lib/minio_runner.rb
152
+ - lib/minio_runner/base_binary.rb
153
+ - lib/minio_runner/binary_manager.rb
154
+ - lib/minio_runner/child_process.rb
155
+ - lib/minio_runner/config.rb
156
+ - lib/minio_runner/mc_binary.rb
157
+ - lib/minio_runner/mc_manager.rb
158
+ - lib/minio_runner/minio_binary.rb
159
+ - lib/minio_runner/minio_server_manager.rb
160
+ - lib/minio_runner/network.rb
161
+ - lib/minio_runner/system.rb
162
+ - lib/minio_runner/version.rb
163
+ homepage: https://rubygemspec.org/gems/minio_runner
164
+ licenses:
165
+ - MIT
166
+ metadata: {}
167
+ post_install_message:
168
+ rdoc_options: []
169
+ require_paths:
170
+ - lib
171
+ required_ruby_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ required_rubygems_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ requirements: []
182
+ rubygems_version: 3.1.6
183
+ signing_key:
184
+ specification_version: 4
185
+ summary: Manages local minio binary installs
186
+ test_files: []