envirobly-orchestra 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +9 -0
- data/bin/orchestra +8 -0
- data/lib/orchestra/base.rb +44 -0
- data/lib/orchestra/cli/images.rb +59 -0
- data/lib/orchestra/cli/main.rb +20 -0
- data/lib/orchestra/cli/services.rb +171 -0
- data/lib/orchestra/cli/stack.rb +6 -0
- data/lib/orchestra/cli.rb +1 -0
- data/lib/orchestra/logger.rb +28 -0
- data/lib/orchestra/version.rb +3 -0
- data/lib/orchestra.rb +10 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 27540543de48daec3396d69ad5ce05079fefd25a0f76a092c914a8e2873a80e7
|
4
|
+
data.tar.gz: 96d0c5e27e028b8c0cc06df90c81bc111a5efe4d591c1926d201773443bc9e11
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 803c22a7247aa5e69a133436924146cb94d2d7d7f56258e446154fac304fcfdba4d4b60f84a1b9407af1505d12eb40946bb515ce19f1a6354b6ae73f178fc82a
|
7
|
+
data.tar.gz: d16b2d572fbb2acd1357c733af6083ea5020cc82eee376c771ffcab3ec5a795bccd9cb948f558247fcec5c27e02950a44add0d7247d0f497da8bb1f856d6bb55
|
data/LICENSE
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Robert Starsi
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
|
+
|
7
|
+
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/orchestra
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
class Orchestra::Base < Thor
|
4
|
+
def self.exit_on_failure?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
LOCKFILE_PATH = "/tmp/orchestra.lock"
|
10
|
+
LOCK_RETRY_INTERVAL = 2
|
11
|
+
def with_lock
|
12
|
+
File.open(LOCKFILE_PATH, File::CREAT | File::EXCL | File::WRONLY) do
|
13
|
+
yield
|
14
|
+
end
|
15
|
+
rescue Errno::EEXIST
|
16
|
+
log "Execution lock in place, retrying in #{LOCK_RETRY_INTERVAL}s"
|
17
|
+
|
18
|
+
sleep LOCK_RETRY_INTERVAL
|
19
|
+
|
20
|
+
retry
|
21
|
+
ensure
|
22
|
+
if File.exist? LOCKFILE_PATH
|
23
|
+
File.delete LOCKFILE_PATH
|
24
|
+
|
25
|
+
log "lock released"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def log(text)
|
30
|
+
Orchestra::Logger.instance.log "inv-#{Thread.current[:invocation]} #{text}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def execute(cmd, exit_on_failure: true)
|
34
|
+
stdout_and_err, status = Open3.capture2e *cmd
|
35
|
+
|
36
|
+
log stdout_and_err unless stdout_and_err.blank?
|
37
|
+
|
38
|
+
if exit_on_failure && status.to_i > 0
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
status.to_i == 0
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "open3"
|
2
|
+
require "pathname"
|
3
|
+
require "benchmark"
|
4
|
+
|
5
|
+
class Orchestra::Cli::Images < Orchestra::Base
|
6
|
+
desc "build", "Build and push a Docker image"
|
7
|
+
method_option :git_url, type: :string, required: true
|
8
|
+
method_option :commit_id, type: :string, required: true
|
9
|
+
method_option :cache_bucket, type: :string, required: true
|
10
|
+
method_option :cache_region, type: :string, required: true
|
11
|
+
method_option :image_uri, type: :string, required: true
|
12
|
+
method_option :dockerfile, type: :string, required: true
|
13
|
+
method_option :build_context, type: :string, required: true
|
14
|
+
def build
|
15
|
+
status = 1
|
16
|
+
|
17
|
+
puts "Checking out commit #{options.commit_id}..."
|
18
|
+
|
19
|
+
checkout_time = Benchmark.realtime do
|
20
|
+
output, status =
|
21
|
+
Open3.capture2e "envirobly-git-checkout-commit", git_checkout_path.to_s, options.git_url, options.commit_id
|
22
|
+
|
23
|
+
puts output
|
24
|
+
end
|
25
|
+
|
26
|
+
puts "Checkout finished in #{checkout_time.to_i}s\n"
|
27
|
+
|
28
|
+
$stdout.flush
|
29
|
+
|
30
|
+
exit 1 if status.to_i > 0
|
31
|
+
|
32
|
+
exec *buildx_build_cmd
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def buildx_build_cmd
|
37
|
+
[
|
38
|
+
"docker", "buildx", "build",
|
39
|
+
"--progress=plain",
|
40
|
+
"--cache-from=type=s3,region=#{options.cache_region},bucket=#{options.cache_bucket},name=app",
|
41
|
+
"--cache-to=type=s3,region=#{options.cache_region},bucket=#{options.cache_bucket},name=app,mode=max",
|
42
|
+
"-t", options.image_uri, "--push",
|
43
|
+
"-f", dockerfile_path.to_s,
|
44
|
+
build_context.to_s
|
45
|
+
]
|
46
|
+
end
|
47
|
+
|
48
|
+
def git_checkout_path
|
49
|
+
Pathname.new "/tmp/build"
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_context
|
53
|
+
git_checkout_path.join options.build_context
|
54
|
+
end
|
55
|
+
|
56
|
+
def dockerfile_path
|
57
|
+
git_checkout_path.join options.dockerfile
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Orchestra::Cli::Main < Orchestra::Base
|
2
|
+
desc "version", "Show Orchestra version"
|
3
|
+
def version
|
4
|
+
puts Orchestra::VERSION
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "docker-version", "Show Docker version"
|
8
|
+
def docker_version
|
9
|
+
puts `docker -v`
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "services", "Manage services"
|
13
|
+
subcommand "services", Orchestra::Cli::Services
|
14
|
+
|
15
|
+
desc "images", "Docker images"
|
16
|
+
subcommand "images", Orchestra::Cli::Images
|
17
|
+
|
18
|
+
desc "stack", "Manage stack"
|
19
|
+
subcommand "stack", Orchestra::Cli::Stack
|
20
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require "open3"
|
2
|
+
# require "httpx"
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
class Orchestra::Cli::Services < Orchestra::Base
|
6
|
+
desc "up", "Start services based on config synced from config bucket"
|
7
|
+
method_option :config_dir, type: :string, required: true
|
8
|
+
method_option :config_bucket, type: :string, required: true
|
9
|
+
method_option :config_region, type: :string, required: true
|
10
|
+
def up
|
11
|
+
with_lock do
|
12
|
+
log "services up"
|
13
|
+
|
14
|
+
execute sync_config_files_cmd
|
15
|
+
execute compose_prepare_cmd
|
16
|
+
initialize_data_volumes
|
17
|
+
execute compose_up_cmd
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "down", "Stop services defined in supplied compose file"
|
22
|
+
method_option :config_dir, type: :string, required: true
|
23
|
+
def down
|
24
|
+
with_lock do
|
25
|
+
log "services down"
|
26
|
+
|
27
|
+
execute compose_down_cmd
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "lock_test", "Test locking"
|
32
|
+
def lock_test
|
33
|
+
with_lock do
|
34
|
+
log "Executing something with lock for 15 seconds..."
|
35
|
+
sleep 15
|
36
|
+
log "Lock test done."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
# def list_containers
|
42
|
+
# http = HTTPX.with(transport: "unix", addresses: ["/var/run/docker.sock"])
|
43
|
+
|
44
|
+
# response = http.get("http://localhost/v1.43/containers/json")
|
45
|
+
|
46
|
+
# response.body
|
47
|
+
# end
|
48
|
+
|
49
|
+
# https://docs.docker.com/engine/reference/commandline/compose_up/
|
50
|
+
def compose_up_cmd
|
51
|
+
[
|
52
|
+
"docker", "compose",
|
53
|
+
"-f", compose_file_path,
|
54
|
+
"up",
|
55
|
+
"--quiet-pull",
|
56
|
+
"--remove-orphans",
|
57
|
+
"--detach",
|
58
|
+
"--wait"
|
59
|
+
]
|
60
|
+
end
|
61
|
+
|
62
|
+
def compose_prepare_cmd
|
63
|
+
[
|
64
|
+
"docker", "compose",
|
65
|
+
"-f", compose_file_path,
|
66
|
+
"up",
|
67
|
+
"--quiet-pull",
|
68
|
+
"--detach",
|
69
|
+
"--no-start"
|
70
|
+
]
|
71
|
+
end
|
72
|
+
|
73
|
+
def compose_down_cmd
|
74
|
+
[
|
75
|
+
"docker", "compose",
|
76
|
+
"-f", compose_file_path,
|
77
|
+
"down",
|
78
|
+
"--remove-orphans"
|
79
|
+
]
|
80
|
+
end
|
81
|
+
|
82
|
+
def compose_file_path
|
83
|
+
Pathname.new(options.config_dir).join("compose.yml").to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
def sync_config_files_cmd
|
87
|
+
[
|
88
|
+
"aws", "s3",
|
89
|
+
"cp", "--recursive", "--no-progress",
|
90
|
+
"--region", options.config_region,
|
91
|
+
"s3://#{options.config_bucket}",
|
92
|
+
options.config_dir
|
93
|
+
]
|
94
|
+
end
|
95
|
+
|
96
|
+
def compose_definition
|
97
|
+
@compose_definition ||= YAML.load ERB.new(File.read(compose_file_path)).result, aliases: true
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize_data_volumes
|
101
|
+
compose_definition["services"].each do |_, service|
|
102
|
+
next if service.dig("labels", "envirobly.data-volume.pool").blank?
|
103
|
+
|
104
|
+
zpool = service["labels"]["envirobly.data-volume.pool"]
|
105
|
+
mountpoint = service["labels"]["envirobly.data-volume.pool.mountpoint"]
|
106
|
+
zfs_dataset = service["labels"]["envirobly.data-volume.dataset"]
|
107
|
+
dataset_mountpoint = service["labels"]["envirobly.data-volume.dataset.mountpoint"]
|
108
|
+
device = service["labels"]["envirobly.data-volume.device"]
|
109
|
+
|
110
|
+
if zfs_dataset_exist?(zfs_dataset)
|
111
|
+
log " ZFS dataset exists"
|
112
|
+
next
|
113
|
+
end
|
114
|
+
|
115
|
+
log "Looking for block device '#{device}'..."
|
116
|
+
|
117
|
+
wait_for_block_device device
|
118
|
+
|
119
|
+
log "Block device '#{device}' found"
|
120
|
+
|
121
|
+
if execute zpool_import_cmd(zpool), exit_on_failure: false
|
122
|
+
log "ZFS pool '#{zpool}' successfully imported"
|
123
|
+
else
|
124
|
+
execute zpool_create_cmd(zpool, device, mountpoint:)
|
125
|
+
execute set_zpool_compression_cmd(zpool)
|
126
|
+
execute create_zfs_dataset_cmd(zfs_dataset)
|
127
|
+
chmod_zfs_dataset_mountpoint(dataset_mountpoint)
|
128
|
+
|
129
|
+
log "Created new ZFS pool and dataset '#{zfs_dataset}'"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def zfs_dataset_exist?(dataset)
|
135
|
+
execute ["zfs", "list", dataset], exit_on_failure: false
|
136
|
+
end
|
137
|
+
|
138
|
+
def wait_for_block_device(device)
|
139
|
+
counter = 1
|
140
|
+
|
141
|
+
while !File.blockdev?(device) do
|
142
|
+
if counter > 15
|
143
|
+
log "Block device '#{device}' not found within 15s. Aborting"
|
144
|
+
exit 1
|
145
|
+
end
|
146
|
+
|
147
|
+
counter += 1
|
148
|
+
sleep 1
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def zpool_import_cmd(pool)
|
153
|
+
["zpool", "import", pool]
|
154
|
+
end
|
155
|
+
|
156
|
+
def zpool_create_cmd(pool, device, mountpoint:)
|
157
|
+
["zpool", "create", "-m", mountpoint, pool, device]
|
158
|
+
end
|
159
|
+
|
160
|
+
def set_zpool_compression_cmd(pool)
|
161
|
+
["zfs", "set", "compression=lz4", pool]
|
162
|
+
end
|
163
|
+
|
164
|
+
def create_zfs_dataset_cmd(dataset)
|
165
|
+
["zfs", "create", dataset]
|
166
|
+
end
|
167
|
+
|
168
|
+
def chmod_zfs_dataset_mountpoint(path)
|
169
|
+
FileUtils.chmod 0777, path
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
module Orchestra::Cli; end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
class Orchestra::Logger
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
LOG_FILE_PATH = "/var/log/orchestra.log"
|
7
|
+
TIMESTAMP_FORMAT = "%Y%m%dT%H%M%SZ"
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@log_file = File.open(LOG_FILE_PATH, "a")
|
11
|
+
end
|
12
|
+
|
13
|
+
def log(text)
|
14
|
+
timestamp = Time.now.strftime(TIMESTAMP_FORMAT)
|
15
|
+
log_message = "#{timestamp} #{text}"
|
16
|
+
|
17
|
+
# Append to the log file
|
18
|
+
@log_file.puts(log_message)
|
19
|
+
@log_file.flush
|
20
|
+
|
21
|
+
# Print to stdout
|
22
|
+
puts log_message
|
23
|
+
end
|
24
|
+
|
25
|
+
def close_log_file
|
26
|
+
@log_file.close
|
27
|
+
end
|
28
|
+
end
|
data/lib/orchestra.rb
ADDED
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: envirobly-orchestra
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Robert Starsi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-01-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: zeitwerk
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.6'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: debug
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.8'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.8'
|
69
|
+
description:
|
70
|
+
email: klevo@klevo.sk
|
71
|
+
executables:
|
72
|
+
- orchestra
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- LICENSE
|
77
|
+
- bin/orchestra
|
78
|
+
- lib/orchestra.rb
|
79
|
+
- lib/orchestra/base.rb
|
80
|
+
- lib/orchestra/cli.rb
|
81
|
+
- lib/orchestra/cli/images.rb
|
82
|
+
- lib/orchestra/cli/main.rb
|
83
|
+
- lib/orchestra/cli/services.rb
|
84
|
+
- lib/orchestra/cli/stack.rb
|
85
|
+
- lib/orchestra/logger.rb
|
86
|
+
- lib/orchestra/version.rb
|
87
|
+
homepage: https://klevo.sk
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
metadata: {}
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubygems_version: 3.5.4
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: Container orchestration agent.
|
110
|
+
test_files: []
|