minio_runner 0.1.0

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