devbox_launcher 0.3.2 → 0.5.0

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