devbox_launcher 0.1.0 → 0.3.3
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 +4 -4
- data/.gitignore +0 -0
- data/.rspec +0 -0
- data/.travis.yml +0 -0
- data/CHANGELOG.md +22 -2
- data/CODE_OF_CONDUCT.md +0 -0
- data/Gemfile +0 -0
- data/Gemfile.lock +19 -15
- data/LICENSE.txt +0 -0
- data/README.md +2 -0
- data/Rakefile +0 -0
- data/devbox_launcher.gemspec +12 -8
- data/lib/devbox_launcher.rb +8 -0
- data/lib/devbox_launcher/box.rb +205 -0
- data/lib/devbox_launcher/cli.rb +1 -118
- data/lib/devbox_launcher/models/box.rb +222 -0
- data/lib/devbox_launcher/models/description.rb +32 -0
- data/lib/devbox_launcher/models/mutagen.rb +23 -0
- data/lib/devbox_launcher/version.rb +1 -1
- data/lib/devbox_launcher/watchman.rb +50 -0
- metadata +64 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7ed54fc2716507cd48b6e65acb08d38f1399c7d2a626a898ea0bdbeb70317f9d
|
|
4
|
+
data.tar.gz: 13b0d4ba4c3b9ae2515040bddff831b746de65c1296d57be20df82bc7f99f722
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c9720ff3bc730ad33b4d225a24796eca616615534d23f5d01c57be8c8291f0b37a0fc2d13092b2f84799c66ea513d6cc3b00bef3fd3e3559fe8d6ea107596e2
|
|
7
|
+
data.tar.gz: a040326a3fe949841970a3d5fd26535dc2cbf06bec09bd30098f63744ff87ac7fd1184b3a79d4848571681b7cad7c91c6ebe89753781f6126c8b5372619790de
|
data/.gitignore
CHANGED
|
File without changes
|
data/.rspec
CHANGED
|
File without changes
|
data/.travis.yml
CHANGED
|
File without changes
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## [
|
|
7
|
+
## [0.3.3]
|
|
8
|
+
### Fixed
|
|
9
|
+
- Support launching multiple boxes at the same time
|
|
10
|
+
|
|
11
|
+
## [0.3.2]
|
|
12
|
+
### Fixed
|
|
13
|
+
- Fix: add missing file
|
|
14
|
+
|
|
15
|
+
## [0.3.1]
|
|
16
|
+
### Fixed
|
|
17
|
+
- Recover when the devbox is in a shutdown cycle
|
|
18
|
+
|
|
19
|
+
## [0.3.0]
|
|
20
|
+
### Changed
|
|
21
|
+
- Label sessions with devbox
|
|
22
|
+
- On linux, mutagen relies on watchman to detect changes
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- In Linux, mutagen no longer burns CPU every 10 seconds
|
|
26
|
+
|
|
27
|
+
## [0.2.0]
|
|
8
28
|
### Added
|
|
9
|
-
- Initial release
|
|
29
|
+
- Initial release
|
data/CODE_OF_CONDUCT.md
CHANGED
|
File without changes
|
data/Gemfile
CHANGED
|
File without changes
|
data/Gemfile.lock
CHANGED
|
@@ -1,34 +1,37 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
devbox_launcher (0.
|
|
5
|
-
activesupport
|
|
6
|
-
bcrypt_pbkdf
|
|
7
|
-
ed25519
|
|
8
|
-
net-ssh
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
devbox_launcher (0.3.3)
|
|
5
|
+
activesupport (~> 6.0)
|
|
6
|
+
bcrypt_pbkdf (~> 1.0)
|
|
7
|
+
ed25519 (~> 1.2)
|
|
8
|
+
net-ssh (~> 5.2)
|
|
9
|
+
os (~> 1.0)
|
|
10
|
+
ruby-watchman (= 0.0.2)
|
|
11
|
+
ssh-config (= 0.1.3)
|
|
12
|
+
thor (~> 1.0)
|
|
11
13
|
|
|
12
14
|
GEM
|
|
13
15
|
remote: https://rubygems.org/
|
|
14
16
|
specs:
|
|
15
|
-
activesupport (6.0.
|
|
17
|
+
activesupport (6.0.3.1)
|
|
16
18
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
17
19
|
i18n (>= 0.7, < 2)
|
|
18
20
|
minitest (~> 5.1)
|
|
19
21
|
tzinfo (~> 1.1)
|
|
20
|
-
zeitwerk (~> 2.
|
|
22
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
|
21
23
|
bcrypt_pbkdf (1.0.1)
|
|
22
24
|
byebug (11.0.1)
|
|
23
25
|
coderay (1.1.2)
|
|
24
|
-
concurrent-ruby (1.1.
|
|
26
|
+
concurrent-ruby (1.1.6)
|
|
25
27
|
diff-lcs (1.3)
|
|
26
28
|
ed25519 (1.2.4)
|
|
27
|
-
i18n (1.
|
|
29
|
+
i18n (1.8.2)
|
|
28
30
|
concurrent-ruby (~> 1.0)
|
|
29
31
|
method_source (0.9.2)
|
|
30
|
-
minitest (5.
|
|
32
|
+
minitest (5.14.1)
|
|
31
33
|
net-ssh (5.2.0)
|
|
34
|
+
os (1.1.0)
|
|
32
35
|
pry (0.12.2)
|
|
33
36
|
coderay (~> 1.1.0)
|
|
34
37
|
method_source (~> 0.9.0)
|
|
@@ -49,12 +52,13 @@ GEM
|
|
|
49
52
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
50
53
|
rspec-support (~> 3.9.0)
|
|
51
54
|
rspec-support (3.9.0)
|
|
55
|
+
ruby-watchman (0.0.2)
|
|
52
56
|
ssh-config (0.1.3)
|
|
53
57
|
thor (1.0.1)
|
|
54
58
|
thread_safe (0.3.6)
|
|
55
|
-
tzinfo (1.2.
|
|
59
|
+
tzinfo (1.2.7)
|
|
56
60
|
thread_safe (~> 0.1)
|
|
57
|
-
zeitwerk (2.
|
|
61
|
+
zeitwerk (2.3.0)
|
|
58
62
|
|
|
59
63
|
PLATFORMS
|
|
60
64
|
ruby
|
|
@@ -67,4 +71,4 @@ DEPENDENCIES
|
|
|
67
71
|
rspec (~> 3.0)
|
|
68
72
|
|
|
69
73
|
BUNDLED WITH
|
|
70
|
-
2.
|
|
74
|
+
2.1.4
|
data/LICENSE.txt
CHANGED
|
File without changes
|
data/README.md
CHANGED
|
@@ -38,6 +38,8 @@ devbox start your-username
|
|
|
38
38
|
|
|
39
39
|
If you want to mosh in immediately, add the `--mosh` switch.
|
|
40
40
|
|
|
41
|
+
Note: Linux users that sync mutagen sessions need to install [Watchman](https://facebook.github.io/watchman/).
|
|
42
|
+
|
|
41
43
|
## Development
|
|
42
44
|
|
|
43
45
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/Rakefile
CHANGED
|
File without changes
|
data/devbox_launcher.gemspec
CHANGED
|
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
|
14
14
|
|
|
15
15
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
16
16
|
|
|
17
|
-
spec.metadata["homepage_uri"] =
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
18
|
spec.metadata["source_code_uri"] = "https://github.com/bloom-solutions/devbox_launcher"
|
|
19
19
|
spec.metadata["changelog_uri"] = "https://github.com/bloom-solutions/devbox_launcher/blob/master/CHANGELOG.md"
|
|
20
20
|
|
|
@@ -24,17 +24,21 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
25
25
|
end
|
|
26
26
|
spec.bindir = "bin"
|
|
27
|
-
spec.executables
|
|
27
|
+
spec.executables =
|
|
28
|
+
spec.files.grep(%r{^exe/}) { |f| File.basename(f) } +
|
|
29
|
+
spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
28
30
|
spec.require_paths = ["lib"]
|
|
29
31
|
|
|
30
32
|
spec.add_development_dependency "bundler", "~> 2.0"
|
|
31
33
|
spec.add_development_dependency "rake", "~> 10.0"
|
|
32
34
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
33
35
|
|
|
34
|
-
spec.add_runtime_dependency "thor"
|
|
35
|
-
spec.add_runtime_dependency "net-ssh"
|
|
36
|
-
spec.add_runtime_dependency "ed25519"
|
|
37
|
-
spec.add_runtime_dependency "bcrypt_pbkdf"
|
|
38
|
-
spec.add_runtime_dependency "ssh-config"
|
|
39
|
-
spec.add_runtime_dependency "activesupport"
|
|
36
|
+
spec.add_runtime_dependency "thor", "~> 1.0"
|
|
37
|
+
spec.add_runtime_dependency "net-ssh", "~> 5.2"
|
|
38
|
+
spec.add_runtime_dependency "ed25519", "~> 1.2"
|
|
39
|
+
spec.add_runtime_dependency "bcrypt_pbkdf", "~> 1.0"
|
|
40
|
+
spec.add_runtime_dependency "ssh-config", "0.1.3"
|
|
41
|
+
spec.add_runtime_dependency "activesupport", "~> 6.0"
|
|
42
|
+
spec.add_runtime_dependency "os", "~> 1.0"
|
|
43
|
+
spec.add_runtime_dependency "ruby-watchman", "0.0.2"
|
|
40
44
|
end
|
data/lib/devbox_launcher.rb
CHANGED
|
@@ -3,6 +3,10 @@ require "ssh-config"
|
|
|
3
3
|
require "open3"
|
|
4
4
|
require "thor"
|
|
5
5
|
require "net/ssh"
|
|
6
|
+
require "os"
|
|
7
|
+
require "ruby-watchman"
|
|
8
|
+
require 'socket'
|
|
9
|
+
require 'pathname'
|
|
6
10
|
require "yaml"
|
|
7
11
|
require "devbox_launcher/version"
|
|
8
12
|
|
|
@@ -11,3 +15,7 @@ module DevboxLauncher
|
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
require "devbox_launcher/cli"
|
|
18
|
+
require "devbox_launcher/watchman"
|
|
19
|
+
require "devbox_launcher/models/description"
|
|
20
|
+
require "devbox_launcher/models/mutagen"
|
|
21
|
+
require "devbox_launcher/models/box"
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
module DevboxLauncher
|
|
2
|
+
class Box
|
|
3
|
+
|
|
4
|
+
WAIT_BOOT_RESCUED_EXCEPTIONS = [
|
|
5
|
+
Net::SSH::ConnectionTimeout,
|
|
6
|
+
Net::SSH::Disconnect,
|
|
7
|
+
Errno::ECONNRESET,
|
|
8
|
+
Errno::ETIMEDOUT,
|
|
9
|
+
]
|
|
10
|
+
WAIT_BOOT_IN_SECONDS = 10.freeze
|
|
11
|
+
DEFAULT_IDENTIFY_FILE_PATH = "~/.ssh/google_compute_engine".freeze
|
|
12
|
+
SSH_CONFIG_PATH = File.expand_path("~/.ssh/config").freeze
|
|
13
|
+
CONFIG_PATH = File.expand_path("~/.devbox_launcher.yml").freeze
|
|
14
|
+
CONFIG = YAML.load_file(CONFIG_PATH).freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :account
|
|
17
|
+
|
|
18
|
+
def initialize(account)
|
|
19
|
+
@account = account
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def start
|
|
23
|
+
start_stdout, start_stderr, start_status =
|
|
24
|
+
Open3.capture3(start_cmd)
|
|
25
|
+
|
|
26
|
+
set_ssh_config!(hostname, {
|
|
27
|
+
username: username,
|
|
28
|
+
ip: description.ip,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
wait_boot
|
|
32
|
+
|
|
33
|
+
reset_mutagen_session(
|
|
34
|
+
mutagen_config: config[:mutagen],
|
|
35
|
+
hostname: hostname,
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def start_cmd
|
|
40
|
+
args = {
|
|
41
|
+
project: config[:project],
|
|
42
|
+
account: account,
|
|
43
|
+
}.map do |(key, val)|
|
|
44
|
+
["--#{key}", val].join("=")
|
|
45
|
+
end.join(" ")
|
|
46
|
+
|
|
47
|
+
[
|
|
48
|
+
"gcloud",
|
|
49
|
+
"compute",
|
|
50
|
+
"instances",
|
|
51
|
+
"start",
|
|
52
|
+
name,
|
|
53
|
+
args
|
|
54
|
+
].join(" ")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def wait_boot(tries: 1)
|
|
58
|
+
Net::SSH.start(hostname, username, timeout: WAIT_BOOT_IN_SECONDS) do |ssh|
|
|
59
|
+
puts "[#{ssh.exec!('date').chomp}] Machine booted"
|
|
60
|
+
end
|
|
61
|
+
rescue *WAIT_BOOT_RESCUED_EXCEPTIONS
|
|
62
|
+
puts "Not booted. Waiting #{WAIT_BOOT_IN_SECONDS} seconds before trying again..."
|
|
63
|
+
|
|
64
|
+
sleep WAIT_BOOT_IN_SECONDS
|
|
65
|
+
|
|
66
|
+
description = describe(name)
|
|
67
|
+
if !description.running?
|
|
68
|
+
puts "Detected that the machine is not running " \
|
|
69
|
+
"(status is #{description.status}). Booting it..."
|
|
70
|
+
start_box name, username
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
wait_boot name, username, tries: tries+1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def description(reload: false)
|
|
77
|
+
return @description if !reload && @description
|
|
78
|
+
|
|
79
|
+
puts "Fetching box's description..."
|
|
80
|
+
|
|
81
|
+
describe_command = %Q(gcloud compute instances describe #{name})
|
|
82
|
+
describe_stdout, describe_stderr, describe_status =
|
|
83
|
+
Open3.capture3(describe_command)
|
|
84
|
+
|
|
85
|
+
if !describe_status.success?
|
|
86
|
+
msg = "Problem fetching the description of #{name}. "
|
|
87
|
+
msg += "Please ensure you can call `#{describe_command}`.\n"
|
|
88
|
+
msg += "Error:\n"
|
|
89
|
+
msg += describe_stderr
|
|
90
|
+
fail msg
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
@description = Description.new(describe_stdout)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def set_ssh_config!(hostname, username:, ip:)
|
|
97
|
+
FileUtils.touch(SSH_CONFIG_PATH)
|
|
98
|
+
config = ConfigFile.new
|
|
99
|
+
args = {
|
|
100
|
+
"HostName" => ip,
|
|
101
|
+
"User" => username,
|
|
102
|
+
"IdentityFile" => DEFAULT_IDENTIFY_FILE_PATH,
|
|
103
|
+
}
|
|
104
|
+
args.each do |key, value|
|
|
105
|
+
config.set(hostname, key, value)
|
|
106
|
+
end
|
|
107
|
+
config.save
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def reset_mutagen_session
|
|
111
|
+
mutagen_config = config[:mutagen]
|
|
112
|
+
return if mutagen_config.nil?
|
|
113
|
+
|
|
114
|
+
alpha_dir = mutagen_config[:alpha]
|
|
115
|
+
beta_dir = mutagen_config[:beta]
|
|
116
|
+
|
|
117
|
+
return if alpha_dir.nil? || beta_dir.nil?
|
|
118
|
+
|
|
119
|
+
terminate_mutagen_session
|
|
120
|
+
|
|
121
|
+
create_mutagen_session(
|
|
122
|
+
alpha_dir: alpha_dir,
|
|
123
|
+
beta_dir: beta_dir,
|
|
124
|
+
hostname: hostname,
|
|
125
|
+
username: username,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if OS.linux?
|
|
129
|
+
watch_alpha(alpha_dir: alpha_dir, hostname: hostname)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def terminate_mutagen_session(username)
|
|
134
|
+
puts "Terminating mutagen session..."
|
|
135
|
+
terminate_mutagen_command =
|
|
136
|
+
%Q(mutagen terminate --label-selector=#{username})
|
|
137
|
+
terminate_mutagen_stdout,
|
|
138
|
+
terminate_mutagen_stderr,
|
|
139
|
+
terminate_mutagen_status =
|
|
140
|
+
Open3.capture3(terminate_mutagen_command)
|
|
141
|
+
|
|
142
|
+
if not terminate_mutagen_status.success?
|
|
143
|
+
# mutagen prints to stdout and stderr
|
|
144
|
+
msg = "Failed to terminate mutagen sessions: " \
|
|
145
|
+
"#{terminate_mutagen_stdout} -" \
|
|
146
|
+
"#{terminate_mutagen_stderr}"
|
|
147
|
+
fail msg
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def create_mutagen_session(alpha_dir:, beta_dir:, hostname:, username:)
|
|
152
|
+
puts "Create mutagen session syncing local #{alpha_dir} " \
|
|
153
|
+
"with #{hostname} #{beta_dir}"
|
|
154
|
+
|
|
155
|
+
create_mutagen_command = [
|
|
156
|
+
"mutagen sync create",
|
|
157
|
+
alpha_dir,
|
|
158
|
+
"#{hostname}:#{beta_dir}",
|
|
159
|
+
"--label=#{username}",
|
|
160
|
+
]
|
|
161
|
+
create_mutagen_command << "--watch-mode-alpha=no-watch" if OS.linux?
|
|
162
|
+
|
|
163
|
+
create_mutagen_stdout,
|
|
164
|
+
create_mutagen_stderr,
|
|
165
|
+
create_mutagen_status =
|
|
166
|
+
Open3.capture3(create_mutagen_command.join(" "))
|
|
167
|
+
|
|
168
|
+
if not create_mutagen_status.success?
|
|
169
|
+
# mutagen prints to stdout and stderr
|
|
170
|
+
msg = "Failed to create mutagen sessions: " \
|
|
171
|
+
"#{create_mutagen_stdout} -" \
|
|
172
|
+
"#{create_mutagen_stderr}"
|
|
173
|
+
fail msg
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def watch_alpha(alpha_dir:, hostname:)
|
|
178
|
+
watchman = Watchman.new(dir: alpha_dir)
|
|
179
|
+
watchman.trigger("mutagen sync flush --label-selector=#{hostname}")
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def name
|
|
183
|
+
@name ||= config[:box]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def hostname
|
|
187
|
+
[name, username, "devbox"].join("-")
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def username
|
|
191
|
+
@username ||= account.gsub(/\W/, "_")
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def config
|
|
195
|
+
return @config if @config
|
|
196
|
+
|
|
197
|
+
if not CONFIG.has_key?(account)
|
|
198
|
+
fail "No config in #{CONFIG_PATH} found for #{account}"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
@config = CONFIG[account].with_indifferent_access
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
end
|
|
205
|
+
end
|
data/lib/devbox_launcher/cli.rb
CHANGED
|
@@ -11,124 +11,7 @@ module DevboxLauncher
|
|
|
11
11
|
option :mosh, type: :boolean, desc: "Mosh in"
|
|
12
12
|
|
|
13
13
|
def start(account)
|
|
14
|
-
|
|
15
|
-
fail "No config in #{CONFIG_PATH} found for #{account}"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
config = CONFIG[account].with_indifferent_access
|
|
19
|
-
|
|
20
|
-
username = account.gsub(/\W/, "_")
|
|
21
|
-
|
|
22
|
-
set_account_command = %Q(gcloud config set account #{account})
|
|
23
|
-
set_account_stdout, set_account_stderr, set_account_status =
|
|
24
|
-
Open3.capture3(set_account_command)
|
|
25
|
-
|
|
26
|
-
set_project_command = %Q(gcloud config set project #{config[:project]})
|
|
27
|
-
set_project_stdout, set_project_stderr, set_project_status =
|
|
28
|
-
Open3.capture3(set_project_command)
|
|
29
|
-
|
|
30
|
-
name = config[:box]
|
|
31
|
-
|
|
32
|
-
start_command = %Q(gcloud compute instances start #{name})
|
|
33
|
-
start_stdout, start_stderr, start_status = Open3.capture3(start_command)
|
|
34
|
-
|
|
35
|
-
puts "Fetching IP..."
|
|
36
|
-
describe_command = %Q(gcloud compute instances describe #{name})
|
|
37
|
-
describe_stdout, describe_stderr, describe_status =
|
|
38
|
-
Open3.capture3(describe_command)
|
|
39
|
-
|
|
40
|
-
if !describe_status.success?
|
|
41
|
-
msg = "Problem fetching the IP address. "
|
|
42
|
-
msg += "Please ensure you can call `#{describe_command}`.\n"
|
|
43
|
-
msg += "Error:\n"
|
|
44
|
-
msg += describe_stderr
|
|
45
|
-
fail msg
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
description = YAML.load(describe_stdout)
|
|
49
|
-
|
|
50
|
-
ip = description["networkInterfaces"].first["accessConfigs"].
|
|
51
|
-
find { |config| config["kind"] == "compute#accessConfig" }["natIP"]
|
|
52
|
-
|
|
53
|
-
puts "IP: #{ip}"
|
|
54
|
-
|
|
55
|
-
hostname = "#{name}-devbox"
|
|
56
|
-
|
|
57
|
-
set_ssh_config!(hostname, {
|
|
58
|
-
username: username,
|
|
59
|
-
ip: ip,
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
wait_boot(hostname, username)
|
|
63
|
-
|
|
64
|
-
mutagen_alpha_dir = config[:mutagen][:alpha] if config[:mutagen]
|
|
65
|
-
mutagen_beta_dir = config[:mutagen][:beta] if config[:mutagen]
|
|
66
|
-
|
|
67
|
-
if [mutagen_alpha_dir, mutagen_beta_dir].all?
|
|
68
|
-
puts "Terminating all mutagen sessions..."
|
|
69
|
-
terminate_mutagen_command = %Q(mutagen terminate --all)
|
|
70
|
-
terminate_mutagen_stdout,
|
|
71
|
-
terminate_mutagen_stderr,
|
|
72
|
-
terminate_mutagen_status =
|
|
73
|
-
Open3.capture3(terminate_mutagen_command)
|
|
74
|
-
|
|
75
|
-
if not terminate_mutagen_status.success?
|
|
76
|
-
# mutagen prints to stdout and stderr
|
|
77
|
-
msg = "Failed to terminate mutagen sessions: " \
|
|
78
|
-
"#{terminate_mutagen_stdout} -" \
|
|
79
|
-
"#{terminate_mutagen_stderr}"
|
|
80
|
-
fail msg
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
puts "Create mutagen session syncing local #{mutagen_alpha_dir} with #{hostname} #{mutagen_beta_dir}"
|
|
84
|
-
create_mutagen_command = [
|
|
85
|
-
"mutagen sync create",
|
|
86
|
-
mutagen_alpha_dir,
|
|
87
|
-
"#{hostname}:#{mutagen_beta_dir}",
|
|
88
|
-
].join(" ")
|
|
89
|
-
create_mutagen_stdout,
|
|
90
|
-
create_mutagen_stderr,
|
|
91
|
-
create_mutagen_status =
|
|
92
|
-
Open3.capture3(create_mutagen_command)
|
|
93
|
-
|
|
94
|
-
if not create_mutagen_status.success?
|
|
95
|
-
# mutagen prints to stdout and stderr
|
|
96
|
-
msg = "Failed to create mutagen sessions: " \
|
|
97
|
-
"#{create_mutagen_stdout} -" \
|
|
98
|
-
"#{create_mutagen_stderr}"
|
|
99
|
-
fail msg
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
if options[:mosh]
|
|
104
|
-
mosh_command = %Q(mosh #{hostname})
|
|
105
|
-
system(mosh_command)
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
no_commands do
|
|
110
|
-
def wait_boot(hostname, username)
|
|
111
|
-
Net::SSH.start(hostname, username, timeout: WAIT_BOOT_IN_SECONDS) do |ssh|
|
|
112
|
-
puts "[#{ssh.exec!('date').chomp}] Machine booted"
|
|
113
|
-
end
|
|
114
|
-
rescue Net::SSH::ConnectionTimeout, Net::SSH::Disconnect, Errno::ECONNRESET
|
|
115
|
-
puts "Not booted. Waiting #{WAIT_BOOT_IN_SECONDS} seconds before trying again..."
|
|
116
|
-
wait_boot hostname, username
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def set_ssh_config!(hostname, username:, ip:)
|
|
120
|
-
FileUtils.touch(SSH_CONFIG_PATH)
|
|
121
|
-
config = ConfigFile.new
|
|
122
|
-
args = {
|
|
123
|
-
"HostName" => ip,
|
|
124
|
-
"User" => username,
|
|
125
|
-
"IdentityFile" => DEFAULT_IDENTIFY_FILE_PATH,
|
|
126
|
-
}
|
|
127
|
-
args.each do |key, value|
|
|
128
|
-
config.set(hostname, key, value)
|
|
129
|
-
end
|
|
130
|
-
config.save
|
|
131
|
-
end
|
|
14
|
+
Box.new(account, options).start
|
|
132
15
|
end
|
|
133
16
|
|
|
134
17
|
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
module DevboxLauncher
|
|
2
|
+
class Box
|
|
3
|
+
|
|
4
|
+
WAIT_BOOT_RESCUED_EXCEPTIONS = [
|
|
5
|
+
Net::SSH::ConnectionTimeout,
|
|
6
|
+
Net::SSH::Disconnect,
|
|
7
|
+
Errno::ECONNRESET,
|
|
8
|
+
Errno::ETIMEDOUT,
|
|
9
|
+
]
|
|
10
|
+
WAIT_BOOT_IN_SECONDS = 10.freeze
|
|
11
|
+
MAX_BOOT_RETRIES = 10
|
|
12
|
+
DEFAULT_IDENTIFY_FILE_PATH = "~/.ssh/google_compute_engine".freeze
|
|
13
|
+
SSH_CONFIG_PATH = File.expand_path("~/.ssh/config").freeze
|
|
14
|
+
CONFIG_PATH = File.expand_path("~/.devbox_launcher.yml").freeze
|
|
15
|
+
CONFIG = YAML.load_file(CONFIG_PATH).freeze
|
|
16
|
+
|
|
17
|
+
attr_reader :account, :options
|
|
18
|
+
|
|
19
|
+
def initialize(account, options)
|
|
20
|
+
@account = account
|
|
21
|
+
@options = options
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def start
|
|
25
|
+
start_stdout, start_stderr, start_status =
|
|
26
|
+
Open3.capture3(start_cmd)
|
|
27
|
+
|
|
28
|
+
set_ssh_config!
|
|
29
|
+
|
|
30
|
+
wait_boot
|
|
31
|
+
|
|
32
|
+
reset_mutagen_session
|
|
33
|
+
|
|
34
|
+
connect_mosh
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def start_cmd
|
|
38
|
+
args = {
|
|
39
|
+
project: config[:project],
|
|
40
|
+
account: account,
|
|
41
|
+
}.map do |(key, val)|
|
|
42
|
+
["--#{key}", val].join("=")
|
|
43
|
+
end.join(" ")
|
|
44
|
+
|
|
45
|
+
[
|
|
46
|
+
"gcloud",
|
|
47
|
+
"compute",
|
|
48
|
+
"instances",
|
|
49
|
+
"start",
|
|
50
|
+
name,
|
|
51
|
+
args
|
|
52
|
+
].join(" ")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def connect_mosh
|
|
56
|
+
return if options[:mosh].nil?
|
|
57
|
+
|
|
58
|
+
mosh_cmd = %Q(mosh #{hostname})
|
|
59
|
+
system(mosh_cmd)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def wait_boot(tries: 1)
|
|
63
|
+
Net::SSH.start(hostname, username, timeout: WAIT_BOOT_IN_SECONDS) do |ssh|
|
|
64
|
+
puts "[#{ssh.exec!('date').chomp}] Machine booted"
|
|
65
|
+
end
|
|
66
|
+
rescue *WAIT_BOOT_RESCUED_EXCEPTIONS
|
|
67
|
+
puts "Not booted. Waiting #{WAIT_BOOT_IN_SECONDS} seconds before trying again..."
|
|
68
|
+
|
|
69
|
+
sleep WAIT_BOOT_IN_SECONDS
|
|
70
|
+
|
|
71
|
+
if !description(reload: true).running?
|
|
72
|
+
puts "Detected that the machine is not running " \
|
|
73
|
+
"(status is #{description.status}). Booting it..."
|
|
74
|
+
start
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
fail if tries >= MAX_BOOT_RETRIES
|
|
78
|
+
|
|
79
|
+
wait_boot tries: tries+1
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def description(reload: false)
|
|
83
|
+
return @description if !reload && @description
|
|
84
|
+
|
|
85
|
+
puts "Fetching box's description..."
|
|
86
|
+
|
|
87
|
+
describe_stdout, describe_stderr, describe_status =
|
|
88
|
+
Open3.capture3(describe_cmd)
|
|
89
|
+
|
|
90
|
+
if !describe_status.success?
|
|
91
|
+
msg = "Problem fetching the description of #{name}. "
|
|
92
|
+
msg += "Please ensure you can call `#{describe_cmd}`.\n"
|
|
93
|
+
msg += "Error:\n"
|
|
94
|
+
msg += describe_stderr
|
|
95
|
+
fail msg
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
@description = Description.new(describe_stdout)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def describe_cmd
|
|
102
|
+
args = {
|
|
103
|
+
project: config[:project],
|
|
104
|
+
account: account,
|
|
105
|
+
}.map do |(key, val)|
|
|
106
|
+
["--#{key}", val].join("=")
|
|
107
|
+
end.join(" ")
|
|
108
|
+
|
|
109
|
+
[
|
|
110
|
+
"gcloud",
|
|
111
|
+
"compute",
|
|
112
|
+
"instances",
|
|
113
|
+
"describe",
|
|
114
|
+
name,
|
|
115
|
+
args
|
|
116
|
+
].join(" ")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def set_ssh_config!
|
|
120
|
+
FileUtils.touch(SSH_CONFIG_PATH)
|
|
121
|
+
config = ConfigFile.new
|
|
122
|
+
args = {
|
|
123
|
+
"HostName" => description.ip,
|
|
124
|
+
"User" => username,
|
|
125
|
+
"IdentityFile" => DEFAULT_IDENTIFY_FILE_PATH,
|
|
126
|
+
}
|
|
127
|
+
args.each do |key, value|
|
|
128
|
+
config.set(hostname, key, value)
|
|
129
|
+
end
|
|
130
|
+
config.save
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def reset_mutagen_session
|
|
134
|
+
return if !mutagen_config.configured?
|
|
135
|
+
|
|
136
|
+
terminate_mutagen_session
|
|
137
|
+
create_mutagen_session
|
|
138
|
+
watch_alpha if OS.linux?
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def terminate_mutagen_session
|
|
142
|
+
puts "Terminating mutagen session..."
|
|
143
|
+
terminate_mutagen_cmd =
|
|
144
|
+
%Q(mutagen terminate --label-selector=#{label})
|
|
145
|
+
terminate_mutagen_stdout,
|
|
146
|
+
terminate_mutagen_stderr,
|
|
147
|
+
terminate_mutagen_status =
|
|
148
|
+
Open3.capture3(terminate_mutagen_cmd)
|
|
149
|
+
|
|
150
|
+
if not terminate_mutagen_status.success?
|
|
151
|
+
# mutagen prints to stdout and stderr
|
|
152
|
+
msg = "Failed to terminate mutagen sessions: " \
|
|
153
|
+
"#{terminate_mutagen_stdout} -" \
|
|
154
|
+
"#{terminate_mutagen_stderr}"
|
|
155
|
+
fail msg
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def label
|
|
160
|
+
"#{username}=#{name}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def create_mutagen_session
|
|
164
|
+
puts "Create mutagen session syncing local " \
|
|
165
|
+
"#{mutagen_config.alpha_dir} with " \
|
|
166
|
+
"#{hostname} #{mutagen_config.beta_dir}"
|
|
167
|
+
|
|
168
|
+
create_mutagen_cmd = [
|
|
169
|
+
"mutagen sync create",
|
|
170
|
+
mutagen_config.alpha_dir,
|
|
171
|
+
"#{hostname}:#{mutagen_config.beta_dir}",
|
|
172
|
+
"--label=#{label}",
|
|
173
|
+
]
|
|
174
|
+
create_mutagen_cmd << "--watch-mode-alpha=no-watch" if OS.linux?
|
|
175
|
+
|
|
176
|
+
create_mutagen_stdout,
|
|
177
|
+
create_mutagen_stderr,
|
|
178
|
+
create_mutagen_status =
|
|
179
|
+
Open3.capture3(create_mutagen_cmd.join(" "))
|
|
180
|
+
|
|
181
|
+
if not create_mutagen_status.success?
|
|
182
|
+
# mutagen prints to stdout and stderr
|
|
183
|
+
msg = "Failed to create mutagen sessions: " \
|
|
184
|
+
"#{create_mutagen_stdout} -" \
|
|
185
|
+
"#{create_mutagen_stderr}"
|
|
186
|
+
fail msg
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def watch_alpha
|
|
191
|
+
watchman = Watchman.new(dir: mutagen_config.alpha_dir)
|
|
192
|
+
watchman.trigger("mutagen sync flush --label-selector=#{label}")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def name
|
|
196
|
+
@name ||= config[:box]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def hostname
|
|
200
|
+
[name, username, "devbox"].join("-")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def username
|
|
204
|
+
@username ||= account.gsub(/\W/, "_")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def config
|
|
208
|
+
return @config if @config
|
|
209
|
+
|
|
210
|
+
if not CONFIG.has_key?(account)
|
|
211
|
+
fail "No config in #{CONFIG_PATH} found for #{account}"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
@config = CONFIG[account].with_indifferent_access
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def mutagen_config
|
|
218
|
+
@mutagen_config ||= Mutagen.new(config[:mutagen])
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
end
|
|
222
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module DevboxLauncher
|
|
2
|
+
class Description
|
|
3
|
+
|
|
4
|
+
def initialize(yaml)
|
|
5
|
+
@desc = YAML.load(yaml)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def ip
|
|
9
|
+
return @ip if @ip
|
|
10
|
+
network_interface = network_interfaces.first
|
|
11
|
+
access_configs = network_interface["accessConfigs"]
|
|
12
|
+
|
|
13
|
+
access_config = access_configs.find do |c|
|
|
14
|
+
c["kind"] == "compute#accessConfig"
|
|
15
|
+
end
|
|
16
|
+
@ip = access_config["natIP"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def status
|
|
20
|
+
@status ||= @desc["status"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def network_interfaces
|
|
24
|
+
@network_interfaces ||= @desc["networkInterfaces"]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def running?
|
|
28
|
+
status == "RUNNING"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module DevboxLauncher
|
|
2
|
+
class Mutagen
|
|
3
|
+
|
|
4
|
+
attr_reader :config
|
|
5
|
+
|
|
6
|
+
def initialize(config)
|
|
7
|
+
@config = config
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def configured?
|
|
11
|
+
[config, alpha_dir, beta_dir].none?(&:nil?)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def alpha_dir
|
|
15
|
+
config[:alpha]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def beta_dir
|
|
19
|
+
config[:beta]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module DevboxLauncher
|
|
2
|
+
class Watchman
|
|
3
|
+
|
|
4
|
+
attr_reader :dir
|
|
5
|
+
|
|
6
|
+
def initialize(dir:)
|
|
7
|
+
@dir = dir
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def trigger(command)
|
|
11
|
+
UNIXSocket.open(sockname) do |socket|
|
|
12
|
+
root = Pathname.new(dir).expand_path.to_s
|
|
13
|
+
result = RubyWatchman.query(['watch-list'], socket)
|
|
14
|
+
roots = result['roots']
|
|
15
|
+
if !roots.include?(root)
|
|
16
|
+
# this path isn't being watched yet; try to set up watch
|
|
17
|
+
result = RubyWatchman.query(['watch-project', root], socket)
|
|
18
|
+
|
|
19
|
+
# root_restrict_files setting may prevent Watchman from working
|
|
20
|
+
raise "Unable to watch #{dir}" if result.has_key?('error')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
query = ['trigger', root, {
|
|
24
|
+
'name' => 'mutagen-sync',
|
|
25
|
+
'expression' => ['match', '**/*', 'wholename'],
|
|
26
|
+
'command' => command.split(" "),
|
|
27
|
+
}]
|
|
28
|
+
paths = RubyWatchman.query(query, socket)
|
|
29
|
+
|
|
30
|
+
# could return error if watch is removed
|
|
31
|
+
if paths.has_key?('error')
|
|
32
|
+
raise "Unable to set trigger. Error: #{paths['error']}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def sockname
|
|
38
|
+
sockname = RubyWatchman.load(
|
|
39
|
+
%x{watchman --output-encoding=bser get-sockname}
|
|
40
|
+
)['sockname']
|
|
41
|
+
|
|
42
|
+
if !$?.exitstatus.zero?
|
|
43
|
+
raise "Failed to connect to watchman. Is it running?"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
sockname
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: devbox_launcher
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ramon Tayag
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-05-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -56,90 +56,121 @@ dependencies:
|
|
|
56
56
|
name: thor
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
|
-
- - "
|
|
59
|
+
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
61
|
+
version: '1.0'
|
|
62
62
|
type: :runtime
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
|
-
- - "
|
|
66
|
+
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
68
|
+
version: '1.0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
70
|
name: net-ssh
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
|
-
- - "
|
|
73
|
+
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
75
|
+
version: '5.2'
|
|
76
76
|
type: :runtime
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
|
-
- - "
|
|
80
|
+
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
82
|
+
version: '5.2'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
84
|
name: ed25519
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
|
-
- - "
|
|
87
|
+
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
89
|
+
version: '1.2'
|
|
90
90
|
type: :runtime
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
|
-
- - "
|
|
94
|
+
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
96
|
+
version: '1.2'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
98
|
name: bcrypt_pbkdf
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
|
-
- - "
|
|
101
|
+
- - "~>"
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0'
|
|
103
|
+
version: '1.0'
|
|
104
104
|
type: :runtime
|
|
105
105
|
prerelease: false
|
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
107
|
requirements:
|
|
108
|
-
- - "
|
|
108
|
+
- - "~>"
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0'
|
|
110
|
+
version: '1.0'
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
112
|
name: ssh-config
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
114
114
|
requirements:
|
|
115
|
-
- -
|
|
115
|
+
- - '='
|
|
116
116
|
- !ruby/object:Gem::Version
|
|
117
|
-
version:
|
|
117
|
+
version: 0.1.3
|
|
118
118
|
type: :runtime
|
|
119
119
|
prerelease: false
|
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
121
|
requirements:
|
|
122
|
-
- -
|
|
122
|
+
- - '='
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
|
-
version:
|
|
124
|
+
version: 0.1.3
|
|
125
125
|
- !ruby/object:Gem::Dependency
|
|
126
126
|
name: activesupport
|
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
|
128
128
|
requirements:
|
|
129
|
-
- - "
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '6.0'
|
|
132
|
+
type: :runtime
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '6.0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: os
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '1.0'
|
|
146
|
+
type: :runtime
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '1.0'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: ruby-watchman
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - '='
|
|
130
158
|
- !ruby/object:Gem::Version
|
|
131
|
-
version:
|
|
159
|
+
version: 0.0.2
|
|
132
160
|
type: :runtime
|
|
133
161
|
prerelease: false
|
|
134
162
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
163
|
requirements:
|
|
136
|
-
- -
|
|
164
|
+
- - '='
|
|
137
165
|
- !ruby/object:Gem::Version
|
|
138
|
-
version:
|
|
166
|
+
version: 0.0.2
|
|
139
167
|
description:
|
|
140
168
|
email:
|
|
141
169
|
- ramon.tayag@gmail.com
|
|
142
|
-
executables:
|
|
170
|
+
executables:
|
|
171
|
+
- console
|
|
172
|
+
- devbox
|
|
173
|
+
- setup
|
|
143
174
|
extensions: []
|
|
144
175
|
extra_rdoc_files: []
|
|
145
176
|
files:
|
|
@@ -158,8 +189,13 @@ files:
|
|
|
158
189
|
- bin/setup
|
|
159
190
|
- devbox_launcher.gemspec
|
|
160
191
|
- lib/devbox_launcher.rb
|
|
192
|
+
- lib/devbox_launcher/box.rb
|
|
161
193
|
- lib/devbox_launcher/cli.rb
|
|
194
|
+
- lib/devbox_launcher/models/box.rb
|
|
195
|
+
- lib/devbox_launcher/models/description.rb
|
|
196
|
+
- lib/devbox_launcher/models/mutagen.rb
|
|
162
197
|
- lib/devbox_launcher/version.rb
|
|
198
|
+
- lib/devbox_launcher/watchman.rb
|
|
163
199
|
homepage: https://github.com/bloom-solutions/devbox_launcher
|
|
164
200
|
licenses:
|
|
165
201
|
- MIT
|
|
@@ -183,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
183
219
|
- !ruby/object:Gem::Version
|
|
184
220
|
version: '0'
|
|
185
221
|
requirements: []
|
|
186
|
-
rubygems_version: 3.
|
|
222
|
+
rubygems_version: 3.0.8
|
|
187
223
|
signing_key:
|
|
188
224
|
specification_version: 4
|
|
189
225
|
summary: Conveniently launch your devbox
|