devbox_launcher 0.3.2 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25588592982c23d00fa42fbd84265d18c9f113ee46bcdb64e9c458115a0b65dc
4
- data.tar.gz: 00560c6344d2f39489f4f6acdb9afc442a90b3d2dc98b98d5ca47fd0085980e2
3
+ metadata.gz: d035caddf255e1498c26b8695f9b1bc538f5e10eebadced13a96854570160627
4
+ data.tar.gz: a8a84b35b07deed752c2d955a0bfa7a7e54c0c1999e8aaa56be3e71437da6733
5
5
  SHA512:
6
- metadata.gz: db2e46803d38b20624480c3dd8dfd9eadcd7fe315841ff6e57fd8b4a70e3752784d38917f8b7d00d41cca8feaadb3bcf44817e3daa7c7885aebdac007ff5fb54
7
- data.tar.gz: 7f1a8c4f5eefd21c6cf32eb0d041b36ac8f495670e9440dfe0079445205c8b02d53db524a182f3c3d7764fe475613552a3e55e064ee52b354103e28045921b1a
6
+ metadata.gz: ee9fabcc46289524f0f6ebdfc1c1725c2e2530bc763e802661a54773ad071bbd225614f1835aab75b3bfef9dbc3c68c6567d396cd3286693319501e109b1134a
7
+ data.tar.gz: 441a937f1606f125f4c18125ac8bc646df62d052dee14fdeb570a7dfcb2d7e56805d5c601e1940a735fd4b448434bbf925fe7b3a571cee1ed7bb38b534a68b76
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
+ ## [0.5.0] - 2021-04-06
8
+ ### Added
9
+ - Ability to specify `zone` in config
10
+
11
+ ## [0.4.0]
12
+ ### Added
13
+ - Sync mutagen with two-way-resolved
14
+
15
+ ## [0.3.5]
16
+ ### Fixed
17
+ - When running commands, also rescue from whitelist of exceptions, and retry
18
+
19
+ ## [0.3.4]
20
+ ### Added
21
+ - Rescue from `Errno::ECONNREFUSED` and retry
22
+
23
+ ## [0.3.3]
24
+ ### Fixed
25
+ - Support launching multiple boxes at the same time
26
+
7
27
  ## [0.3.2]
8
28
  ### Fixed
9
29
  - Fix: add missing file
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- devbox_launcher (0.3.2)
4
+ devbox_launcher (0.5.0)
5
5
  activesupport (~> 6.0)
6
6
  bcrypt_pbkdf (~> 1.0)
7
7
  ed25519 (~> 1.2)
@@ -14,31 +14,31 @@ PATH
14
14
  GEM
15
15
  remote: https://rubygems.org/
16
16
  specs:
17
- activesupport (6.0.3.1)
17
+ activesupport (6.1.3)
18
18
  concurrent-ruby (~> 1.0, >= 1.0.2)
19
- i18n (>= 0.7, < 2)
20
- minitest (~> 5.1)
21
- tzinfo (~> 1.1)
22
- zeitwerk (~> 2.2, >= 2.2.2)
23
- bcrypt_pbkdf (1.0.1)
19
+ i18n (>= 1.6, < 2)
20
+ minitest (>= 5.1)
21
+ tzinfo (~> 2.0)
22
+ zeitwerk (~> 2.3)
23
+ bcrypt_pbkdf (1.1.0)
24
24
  byebug (11.0.1)
25
25
  coderay (1.1.2)
26
- concurrent-ruby (1.1.6)
26
+ concurrent-ruby (1.1.8)
27
27
  diff-lcs (1.3)
28
28
  ed25519 (1.2.4)
29
- i18n (1.8.2)
29
+ i18n (1.8.9)
30
30
  concurrent-ruby (~> 1.0)
31
31
  method_source (0.9.2)
32
- minitest (5.14.1)
32
+ minitest (5.14.4)
33
33
  net-ssh (5.2.0)
34
- os (1.1.0)
34
+ os (1.1.1)
35
35
  pry (0.12.2)
36
36
  coderay (~> 1.1.0)
37
37
  method_source (~> 0.9.0)
38
38
  pry-byebug (3.7.0)
39
39
  byebug (~> 11.0)
40
40
  pry (~> 0.10)
41
- rake (10.5.0)
41
+ rake (13.0.1)
42
42
  rspec (3.9.0)
43
43
  rspec-core (~> 3.9.0)
44
44
  rspec-expectations (~> 3.9.0)
@@ -54,11 +54,10 @@ GEM
54
54
  rspec-support (3.9.0)
55
55
  ruby-watchman (0.0.2)
56
56
  ssh-config (0.1.3)
57
- thor (1.0.1)
58
- thread_safe (0.3.6)
59
- tzinfo (1.2.7)
60
- thread_safe (~> 0.1)
61
- zeitwerk (2.3.0)
57
+ thor (1.1.0)
58
+ tzinfo (2.0.4)
59
+ concurrent-ruby (~> 1.0)
60
+ zeitwerk (2.4.2)
62
61
 
63
62
  PLATFORMS
64
63
  ruby
@@ -67,7 +66,7 @@ DEPENDENCIES
67
66
  bundler (~> 2.0)
68
67
  devbox_launcher!
69
68
  pry-byebug
70
- rake (~> 10.0)
69
+ rake (~> 13.0)
71
70
  rspec (~> 3.0)
72
71
 
73
72
  BUNDLED WITH
data/README.md CHANGED
@@ -4,13 +4,9 @@ Start devboxes quickly
4
4
 
5
5
  ## Installation
6
6
 
7
- Install the gem:
8
-
9
- ```sh
10
- gem install devbox_launcher
11
- ```
12
-
13
- Setup gcloud init with the project that contains your VM.
7
+ - Install the gem: `gem install devbox_launcher`
8
+ - Setup `gcloud init` with the project that contains your VM
9
+ - Install [mutagen](https://mutagen.io)
14
10
 
15
11
  ## Usage
16
12
 
@@ -21,6 +17,9 @@ Create the config file at `~/.devbox_launcher.yml` so you type less. This is an
21
17
  ```yml
22
18
  ramon@email.com:
23
19
  project: general-192303
20
+ # zone not necessarily required, but sometimes starting the box
21
+ # fails without this:
22
+ zone: us-central1-a
24
23
  box: your-instance-name
25
24
  mutagen:
26
25
  alpha: /mnt/c/Users/me/src # local machine
@@ -36,7 +35,7 @@ To start and create the mutagen session:
36
35
  devbox start your-username
37
36
  ```
38
37
 
39
- If you want to mosh in immediately, add the `--mosh` switch.
38
+ If you want to mosh in immediately, add the `--mosh` switch. Yes, mosh needs to be [installed](https://mosh.org/) in your development machine.
40
39
 
41
40
  Note: Linux users that sync mutagen sessions need to install [Watchman](https://facebook.github.io/watchman/).
42
41
 
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.require_paths = ["lib"]
31
31
 
32
32
  spec.add_development_dependency "bundler", "~> 2.0"
33
- spec.add_development_dependency "rake", "~> 10.0"
33
+ spec.add_development_dependency "rake", "~> 13.0"
34
34
  spec.add_development_dependency "rspec", "~> 3.0"
35
35
 
36
36
  spec.add_runtime_dependency "thor", "~> 1.0"
@@ -1,4 +1,5 @@
1
1
  require "active_support/core_ext/hash/indifferent_access"
2
+ require "active_support/core_ext/object/blank"
2
3
  require "ssh-config"
3
4
  require "open3"
4
5
  require "thor"
@@ -17,3 +18,5 @@ end
17
18
  require "devbox_launcher/cli"
18
19
  require "devbox_launcher/watchman"
19
20
  require "devbox_launcher/models/description"
21
+ require "devbox_launcher/models/mutagen"
22
+ require "devbox_launcher/models/box"
@@ -0,0 +1,214 @@
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
+ Errno::ECONNREFUSED,
10
+ ]
11
+ WAIT_BOOT_IN_SECONDS = 10.freeze
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
18
+
19
+ def initialize(account)
20
+ @account = account
21
+ end
22
+
23
+ def start
24
+ start_stdout, start_stderr, start_status =
25
+ run_command(start_cmd)
26
+
27
+ set_ssh_config!(hostname, {
28
+ username: username,
29
+ ip: description.ip,
30
+ })
31
+
32
+ wait_boot
33
+
34
+ reset_mutagen_session(
35
+ mutagen_config: config[:mutagen],
36
+ hostname: hostname,
37
+ )
38
+ end
39
+
40
+ def start_cmd
41
+ args = {
42
+ project: config[:project],
43
+ account: account,
44
+ }.map do |(key, val)|
45
+ ["--#{key}", val].join("=")
46
+ end.join(" ")
47
+
48
+ [
49
+ "gcloud",
50
+ "compute",
51
+ "instances",
52
+ "start",
53
+ name,
54
+ args
55
+ ].join(" ")
56
+ end
57
+
58
+ def wait_boot(tries: 1)
59
+ Net::SSH.start(hostname, username, timeout: WAIT_BOOT_IN_SECONDS) do |ssh|
60
+ puts "[#{ssh.exec!('date').chomp}] Machine booted"
61
+ end
62
+ rescue *WAIT_BOOT_RESCUED_EXCEPTIONS
63
+ puts "Not booted. Waiting #{WAIT_BOOT_IN_SECONDS} seconds before trying again..."
64
+
65
+ sleep WAIT_BOOT_IN_SECONDS
66
+
67
+ description = describe(name)
68
+ if !description.running?
69
+ puts "Detected that the machine is not running " \
70
+ "(status is #{description.status}). Booting it..."
71
+ start_box name, username
72
+ end
73
+
74
+ wait_boot name, username, tries: tries+1
75
+ end
76
+
77
+ def description(reload: false)
78
+ return @description if !reload && @description
79
+
80
+ puts "Fetching box's description..."
81
+
82
+ describe_command = %Q(gcloud compute instances describe #{name})
83
+ describe_stdout, describe_stderr, describe_status =
84
+ run_command(describe_command)
85
+
86
+ if !describe_status.success?
87
+ msg = "Problem fetching the description of #{name}. "
88
+ msg += "Please ensure you can call `#{describe_command}`.\n"
89
+ msg += "Error:\n"
90
+ msg += describe_stderr
91
+ fail msg
92
+ end
93
+
94
+ @description = Description.new(describe_stdout)
95
+ end
96
+
97
+ def set_ssh_config!(hostname, username:, ip:)
98
+ FileUtils.touch(SSH_CONFIG_PATH)
99
+ config = ConfigFile.new
100
+ args = {
101
+ "HostName" => ip,
102
+ "User" => username,
103
+ "IdentityFile" => DEFAULT_IDENTIFY_FILE_PATH,
104
+ }
105
+ args.each do |key, value|
106
+ config.set(hostname, key, value)
107
+ end
108
+ config.save
109
+ end
110
+
111
+ def reset_mutagen_session
112
+ mutagen_config = config[:mutagen]
113
+ return if mutagen_config.nil?
114
+
115
+ alpha_dir = mutagen_config[:alpha]
116
+ beta_dir = mutagen_config[:beta]
117
+
118
+ return if alpha_dir.nil? || beta_dir.nil?
119
+
120
+ terminate_mutagen_session
121
+
122
+ create_mutagen_session(
123
+ alpha_dir: alpha_dir,
124
+ beta_dir: beta_dir,
125
+ hostname: hostname,
126
+ username: username,
127
+ )
128
+
129
+ if OS.linux?
130
+ watch_alpha(alpha_dir: alpha_dir, hostname: hostname)
131
+ end
132
+ end
133
+
134
+ def terminate_mutagen_session(username)
135
+ puts "Terminating mutagen session..."
136
+ terminate_mutagen_command =
137
+ %Q(mutagen terminate --label-selector=#{username})
138
+ terminate_mutagen_stdout,
139
+ terminate_mutagen_stderr,
140
+ terminate_mutagen_status =
141
+ run_command(terminate_mutagen_command)
142
+
143
+ if not terminate_mutagen_status.success?
144
+ # mutagen prints to stdout and stderr
145
+ msg = "Failed to terminate mutagen sessions: " \
146
+ "#{terminate_mutagen_stdout} -" \
147
+ "#{terminate_mutagen_stderr}"
148
+ fail msg
149
+ end
150
+ end
151
+
152
+ def create_mutagen_session(alpha_dir:, beta_dir:, hostname:, username:)
153
+ puts "Create mutagen session syncing local #{alpha_dir} " \
154
+ "with #{hostname} #{beta_dir}"
155
+
156
+ create_mutagen_command = [
157
+ "mutagen sync create",
158
+ alpha_dir,
159
+ "#{hostname}:#{beta_dir}",
160
+ "--label=#{username}",
161
+ "--sync-mode=two-way-resolved",
162
+ ]
163
+ create_mutagen_command << "--watch-mode-alpha=no-watch" if OS.linux?
164
+
165
+ create_mutagen_stdout,
166
+ create_mutagen_stderr,
167
+ create_mutagen_status =
168
+ run_command(create_mutagen_command.join(" "))
169
+
170
+ if not create_mutagen_status.success?
171
+ # mutagen prints to stdout and stderr
172
+ msg = "Failed to create mutagen sessions: " \
173
+ "#{create_mutagen_stdout} -" \
174
+ "#{create_mutagen_stderr}"
175
+ fail msg
176
+ end
177
+ end
178
+
179
+ def watch_alpha(alpha_dir:, hostname:)
180
+ watchman = Watchman.new(dir: alpha_dir)
181
+ watchman.trigger("mutagen sync flush --label-selector=#{hostname}")
182
+ end
183
+
184
+ def name
185
+ @name ||= config[:box]
186
+ end
187
+
188
+ def hostname
189
+ [name, username, "devbox"].join("-")
190
+ end
191
+
192
+ def username
193
+ @username ||= account.gsub(/\W/, "_")
194
+ end
195
+
196
+ def config
197
+ return @config if @config
198
+
199
+ if not CONFIG.has_key?(account)
200
+ fail "No config in #{CONFIG_PATH} found for #{account}"
201
+ end
202
+
203
+ @config = CONFIG[account].with_indifferent_access
204
+ end
205
+
206
+ def run_command(command, tries: 0)
207
+ Open3.capture3(command)
208
+ rescue *WAIT_BOOT_RESCUED_EXCEPTIONS
209
+ sleep WAIT_BOOT_IN_SECONDS
210
+ run_command(command, tries+1)
211
+ end
212
+
213
+ end
214
+ end
@@ -6,186 +6,12 @@ module DevboxLauncher
6
6
  SSH_CONFIG_PATH = File.expand_path("~/.ssh/config").freeze
7
7
  CONFIG_PATH = File.expand_path("~/.devbox_launcher.yml").freeze
8
8
  CONFIG = YAML.load_file(CONFIG_PATH).freeze
9
- LABEL = "devbox".freeze
10
9
 
11
10
  desc "start configured box for account", "Start a devbox by account"
12
11
  option :mosh, type: :boolean, desc: "Mosh in"
13
12
 
14
13
  def start(account)
15
- if not CONFIG.has_key?(account)
16
- fail "No config in #{CONFIG_PATH} found for #{account}"
17
- end
18
-
19
- config = CONFIG[account].with_indifferent_access
20
- name = config[:box]
21
-
22
- username = account.gsub(/\W/, "_")
23
-
24
- puts "Starting #{name}..."
25
-
26
- set_account_command = %Q(gcloud config set account #{account})
27
- set_account_stdout, set_account_stderr, set_account_status =
28
- Open3.capture3(set_account_command)
29
-
30
- set_project_command = %Q(gcloud config set project #{config[:project]})
31
- set_project_stdout, set_project_stderr, set_project_status =
32
- Open3.capture3(set_project_command)
33
-
34
- start_box name, username
35
-
36
- wait_boot(name, username)
37
-
38
- hostname = hostname_for(name)
39
-
40
- reset_mutagen_session(
41
- mutagen_config: config[:mutagen],
42
- hostname: hostname,
43
- )
44
-
45
- if options[:mosh]
46
- mosh_command = %Q(mosh #{hostname})
47
- system(mosh_command)
48
- end
49
- end
50
-
51
- no_commands do
52
- def wait_boot(name, username, tries: 1)
53
- hostname = hostname_for(name)
54
-
55
- Net::SSH.start(hostname, username, timeout: WAIT_BOOT_IN_SECONDS) do |ssh|
56
- puts "[#{ssh.exec!('date').chomp}] Machine booted"
57
- end
58
- rescue Net::SSH::ConnectionTimeout, Net::SSH::Disconnect, Errno::ECONNRESET
59
- puts "Not booted. Waiting #{WAIT_BOOT_IN_SECONDS} seconds before trying again..."
60
-
61
- sleep WAIT_BOOT_IN_SECONDS
62
-
63
- description = describe(name)
64
- if !description.running?
65
- puts "Detected that the machine is not running " \
66
- "(status is #{description.status}). Booting it..."
67
- start_box name, username
68
- end
69
-
70
- wait_boot name, username, tries: tries+1
71
- end
72
-
73
- def set_ssh_config!(hostname, username:, ip:)
74
- FileUtils.touch(SSH_CONFIG_PATH)
75
- config = ConfigFile.new
76
- args = {
77
- "HostName" => ip,
78
- "User" => username,
79
- "IdentityFile" => DEFAULT_IDENTIFY_FILE_PATH,
80
- }
81
- args.each do |key, value|
82
- config.set(hostname, key, value)
83
- end
84
- config.save
85
- end
86
-
87
- def reset_mutagen_session(mutagen_config:, hostname:)
88
- return if mutagen_config.nil?
89
- alpha_dir = mutagen_config[:alpha]
90
- beta_dir = mutagen_config[:beta]
91
-
92
- return if alpha_dir.nil? || beta_dir.nil?
93
-
94
- terminate_mutagen_session
95
- create_mutagen_session(
96
- alpha_dir: alpha_dir,
97
- beta_dir: beta_dir,
98
- hostname: hostname,
99
- )
100
-
101
- if OS.linux?
102
- watch_alpha(alpha_dir: alpha_dir)
103
- end
104
- end
105
-
106
- def terminate_mutagen_session
107
- puts "Terminating mutagen session..."
108
- terminate_mutagen_command =
109
- %Q(mutagen terminate --label-selector=#{LABEL})
110
- terminate_mutagen_stdout,
111
- terminate_mutagen_stderr,
112
- terminate_mutagen_status =
113
- Open3.capture3(terminate_mutagen_command)
114
-
115
- if not terminate_mutagen_status.success?
116
- # mutagen prints to stdout and stderr
117
- msg = "Failed to terminate mutagen sessions: " \
118
- "#{terminate_mutagen_stdout} -" \
119
- "#{terminate_mutagen_stderr}"
120
- fail msg
121
- end
122
- end
123
-
124
- def create_mutagen_session(alpha_dir:, beta_dir:, hostname:)
125
- puts "Create mutagen session syncing local #{alpha_dir} " \
126
- "with #{hostname} #{beta_dir}"
127
-
128
- create_mutagen_command = [
129
- "mutagen sync create",
130
- alpha_dir,
131
- "#{hostname}:#{beta_dir}",
132
- "--label=#{LABEL}",
133
- ]
134
- create_mutagen_command << "--watch-mode-alpha=no-watch" if OS.linux?
135
-
136
- create_mutagen_stdout,
137
- create_mutagen_stderr,
138
- create_mutagen_status =
139
- Open3.capture3(create_mutagen_command.join(" "))
140
-
141
- if not create_mutagen_status.success?
142
- # mutagen prints to stdout and stderr
143
- msg = "Failed to create mutagen sessions: " \
144
- "#{create_mutagen_stdout} -" \
145
- "#{create_mutagen_stderr}"
146
- fail msg
147
- end
148
- end
149
-
150
- def watch_alpha(alpha_dir:)
151
- watchman = Watchman.new(dir: alpha_dir)
152
- watchman.trigger("mutagen sync flush --label-selector=#{LABEL}")
153
- end
154
-
155
- def start_box(name, username)
156
- start_command = %Q(gcloud compute instances start #{name})
157
- start_stdout, start_stderr, start_status = Open3.capture3(start_command)
158
-
159
- desc = describe(name)
160
- ip = desc.ip
161
-
162
- set_ssh_config!(hostname_for(name), {
163
- username: username,
164
- ip: ip,
165
- })
166
- end
167
-
168
- def describe(name)
169
- puts "Fetching box's description..."
170
-
171
- describe_command = %Q(gcloud compute instances describe #{name})
172
- describe_stdout, describe_stderr, describe_status =
173
- Open3.capture3(describe_command)
174
-
175
- if !describe_status.success?
176
- msg = "Problem fetching the description of #{name}. "
177
- msg += "Please ensure you can call `#{describe_command}`.\n"
178
- msg += "Error:\n"
179
- msg += describe_stderr
180
- fail msg
181
- end
182
-
183
- Description.new(describe_stdout)
184
- end
185
-
186
- def hostname_for(name)
187
- [name, "devbox"].join("-")
188
- end
14
+ Box.new(account, options).start
189
15
  end
190
16
 
191
17
  end
@@ -0,0 +1,224 @@
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
+ zone: config[:zone],
42
+ }.each_with_object([]) do |(key, val), arr|
43
+ next if val.blank?
44
+ arr << ["--#{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 connect_mosh
58
+ return if options[:mosh].nil?
59
+
60
+ mosh_cmd = %Q(mosh #{hostname})
61
+ system(mosh_cmd)
62
+ end
63
+
64
+ def wait_boot(tries: 1)
65
+ Net::SSH.start(hostname, username, timeout: WAIT_BOOT_IN_SECONDS) do |ssh|
66
+ puts "[#{ssh.exec!('date').chomp}] Machine booted"
67
+ end
68
+ rescue *WAIT_BOOT_RESCUED_EXCEPTIONS
69
+ puts "Not booted. Waiting #{WAIT_BOOT_IN_SECONDS} seconds before trying again..."
70
+
71
+ sleep WAIT_BOOT_IN_SECONDS
72
+
73
+ if !description(reload: true).running?
74
+ puts "Detected that the machine is not running " \
75
+ "(status is #{description.status}). Booting it..."
76
+ start
77
+ end
78
+
79
+ fail if tries >= MAX_BOOT_RETRIES
80
+
81
+ wait_boot tries: tries+1
82
+ end
83
+
84
+ def description(reload: false)
85
+ return @description if !reload && @description
86
+
87
+ puts "Fetching box's description..."
88
+
89
+ describe_stdout, describe_stderr, describe_status =
90
+ Open3.capture3(describe_cmd)
91
+
92
+ if !describe_status.success?
93
+ msg = "Problem fetching the description of #{name}. "
94
+ msg += "Please ensure you can call `#{describe_cmd}`.\n"
95
+ msg += "Error:\n"
96
+ msg += describe_stderr
97
+ fail msg
98
+ end
99
+
100
+ @description = Description.new(describe_stdout)
101
+ end
102
+
103
+ def describe_cmd
104
+ args = {
105
+ project: config[:project],
106
+ account: account,
107
+ }.map do |(key, val)|
108
+ ["--#{key}", val].join("=")
109
+ end.join(" ")
110
+
111
+ [
112
+ "gcloud",
113
+ "compute",
114
+ "instances",
115
+ "describe",
116
+ name,
117
+ args
118
+ ].join(" ")
119
+ end
120
+
121
+ def set_ssh_config!
122
+ FileUtils.touch(SSH_CONFIG_PATH)
123
+ config = ConfigFile.new
124
+ args = {
125
+ "HostName" => description.ip,
126
+ "User" => username,
127
+ "IdentityFile" => DEFAULT_IDENTIFY_FILE_PATH,
128
+ }
129
+ args.each do |key, value|
130
+ config.set(hostname, key, value)
131
+ end
132
+ config.save
133
+ end
134
+
135
+ def reset_mutagen_session
136
+ return if !mutagen_config.configured?
137
+
138
+ terminate_mutagen_session
139
+ create_mutagen_session
140
+ watch_alpha if OS.linux?
141
+ end
142
+
143
+ def terminate_mutagen_session
144
+ puts "Terminating mutagen session..."
145
+ terminate_mutagen_cmd =
146
+ %Q(mutagen terminate --label-selector=#{label})
147
+ terminate_mutagen_stdout,
148
+ terminate_mutagen_stderr,
149
+ terminate_mutagen_status =
150
+ Open3.capture3(terminate_mutagen_cmd)
151
+
152
+ if not terminate_mutagen_status.success?
153
+ # mutagen prints to stdout and stderr
154
+ msg = "Failed to terminate mutagen sessions: " \
155
+ "#{terminate_mutagen_stdout} -" \
156
+ "#{terminate_mutagen_stderr}"
157
+ fail msg
158
+ end
159
+ end
160
+
161
+ def label
162
+ "#{username}=#{name}"
163
+ end
164
+
165
+ def create_mutagen_session
166
+ puts "Create mutagen session syncing local " \
167
+ "#{mutagen_config.alpha_dir} with " \
168
+ "#{hostname} #{mutagen_config.beta_dir}"
169
+
170
+ create_mutagen_cmd = [
171
+ "mutagen sync create",
172
+ mutagen_config.alpha_dir,
173
+ "#{hostname}:#{mutagen_config.beta_dir}",
174
+ "--label=#{label}",
175
+ ]
176
+ create_mutagen_cmd << "--watch-mode-alpha=no-watch" if OS.linux?
177
+
178
+ create_mutagen_stdout,
179
+ create_mutagen_stderr,
180
+ create_mutagen_status =
181
+ Open3.capture3(create_mutagen_cmd.join(" "))
182
+
183
+ if not create_mutagen_status.success?
184
+ # mutagen prints to stdout and stderr
185
+ msg = "Failed to create mutagen sessions: " \
186
+ "#{create_mutagen_stdout} -" \
187
+ "#{create_mutagen_stderr}"
188
+ fail msg
189
+ end
190
+ end
191
+
192
+ def watch_alpha
193
+ watchman = Watchman.new(dir: mutagen_config.alpha_dir)
194
+ watchman.trigger("mutagen sync flush --label-selector=#{label}")
195
+ end
196
+
197
+ def name
198
+ @name ||= config[:box]
199
+ end
200
+
201
+ def hostname
202
+ [name, username, "devbox"].join("-")
203
+ end
204
+
205
+ def username
206
+ @username ||= account.gsub(/\W/, "_")
207
+ end
208
+
209
+ def config
210
+ return @config if @config
211
+
212
+ if not CONFIG.has_key?(account)
213
+ fail "No config in #{CONFIG_PATH} found for #{account}"
214
+ end
215
+
216
+ @config = CONFIG[account].with_indifferent_access
217
+ end
218
+
219
+ def mutagen_config
220
+ @mutagen_config ||= Mutagen.new(config[:mutagen])
221
+ end
222
+
223
+ end
224
+ 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
@@ -1,3 +1,3 @@
1
1
  module DevboxLauncher
2
- VERSION = "0.3.2"
2
+ VERSION = "0.5.0"
3
3
  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.3.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ramon Tayag
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-25 00:00:00.000000000 Z
11
+ date: 2021-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -164,7 +164,7 @@ dependencies:
164
164
  - - '='
165
165
  - !ruby/object:Gem::Version
166
166
  version: 0.0.2
167
- description:
167
+ description:
168
168
  email:
169
169
  - ramon.tayag@gmail.com
170
170
  executables:
@@ -189,8 +189,11 @@ files:
189
189
  - bin/setup
190
190
  - devbox_launcher.gemspec
191
191
  - lib/devbox_launcher.rb
192
+ - lib/devbox_launcher/box.rb
192
193
  - lib/devbox_launcher/cli.rb
194
+ - lib/devbox_launcher/models/box.rb
193
195
  - lib/devbox_launcher/models/description.rb
196
+ - lib/devbox_launcher/models/mutagen.rb
194
197
  - lib/devbox_launcher/version.rb
195
198
  - lib/devbox_launcher/watchman.rb
196
199
  homepage: https://github.com/bloom-solutions/devbox_launcher
@@ -201,7 +204,7 @@ metadata:
201
204
  homepage_uri: https://github.com/bloom-solutions/devbox_launcher
202
205
  source_code_uri: https://github.com/bloom-solutions/devbox_launcher
203
206
  changelog_uri: https://github.com/bloom-solutions/devbox_launcher/blob/master/CHANGELOG.md
204
- post_install_message:
207
+ post_install_message:
205
208
  rdoc_options: []
206
209
  require_paths:
207
210
  - lib
@@ -216,8 +219,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
216
219
  - !ruby/object:Gem::Version
217
220
  version: '0'
218
221
  requirements: []
219
- rubygems_version: 3.0.8
220
- signing_key:
222
+ rubygems_version: 3.1.4
223
+ signing_key:
221
224
  specification_version: 4
222
225
  summary: Conveniently launch your devbox
223
226
  test_files: []