devbox_launcher 0.5.0 → 0.6.1

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: d035caddf255e1498c26b8695f9b1bc538f5e10eebadced13a96854570160627
4
- data.tar.gz: a8a84b35b07deed752c2d955a0bfa7a7e54c0c1999e8aaa56be3e71437da6733
3
+ metadata.gz: 4eab03a6e4275a1be97d9f2de38761365774b23eb29f6e54c2d96de621c9e17d
4
+ data.tar.gz: 2515a1620a66125fbfab9f8a55f984ab04f103d0cdc350ccc5438030378b4d78
5
5
  SHA512:
6
- metadata.gz: ee9fabcc46289524f0f6ebdfc1c1725c2e2530bc763e802661a54773ad071bbd225614f1835aab75b3bfef9dbc3c68c6567d396cd3286693319501e109b1134a
7
- data.tar.gz: 441a937f1606f125f4c18125ac8bc646df62d052dee14fdeb570a7dfcb2d7e56805d5c601e1940a735fd4b448434bbf925fe7b3a571cee1ed7bb38b534a68b76
6
+ metadata.gz: b1ee272d44787d81de69a0a0d82505ee8f0700abb7f01d32823b2b1588ebeeda88cdfa782918eb215c0edbd639a364af767e74082b35095908b7584109d1d00d
7
+ data.tar.gz: 928e8d2bf7b41988f809475f1d39cfdbcdab5a99a152861108c17bcac11ba410966073212088b07fc7f607db8096d2b32dc932e65779733a90bc40646bc5e062
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,25 @@ 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.6.1] - 2021-09-22
8
+ ### Fixed
9
+ - Do not blow up if mutagen is not configured
10
+
11
+ ## [0.6.0] - 2021-09-21
12
+ ### Changed
13
+ - Ability to configure multiple boxes under one account
14
+
15
+ ## [0.5.2] - 2021-05-31
16
+ ### Added
17
+ - Ignore VCS as recommended by mutagen
18
+
19
+ ### Fixed
20
+ - Retry on `Errno::ECONNREFUSED`
21
+
22
+ ## [0.5.1] - 2021-04-08
23
+ ### Fixed
24
+ - Use configured `zone` for describe as well
25
+
7
26
  ## [0.5.0] - 2021-04-06
8
27
  ### Added
9
28
  - Ability to specify `zone` in config
data/CODE_OF_CONDUCT.md CHANGED
File without changes
data/Gemfile CHANGED
File without changes
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- devbox_launcher (0.5.0)
4
+ devbox_launcher (0.6.1)
5
5
  activesupport (~> 6.0)
6
6
  bcrypt_pbkdf (~> 1.0)
7
7
  ed25519 (~> 1.2)
@@ -14,7 +14,7 @@ PATH
14
14
  GEM
15
15
  remote: https://rubygems.org/
16
16
  specs:
17
- activesupport (6.1.3)
17
+ activesupport (6.1.4.1)
18
18
  concurrent-ruby (~> 1.0, >= 1.0.2)
19
19
  i18n (>= 1.6, < 2)
20
20
  minitest (>= 5.1)
@@ -23,10 +23,10 @@ GEM
23
23
  bcrypt_pbkdf (1.1.0)
24
24
  byebug (11.0.1)
25
25
  coderay (1.1.2)
26
- concurrent-ruby (1.1.8)
26
+ concurrent-ruby (1.1.9)
27
27
  diff-lcs (1.3)
28
28
  ed25519 (1.2.4)
29
- i18n (1.8.9)
29
+ i18n (1.8.10)
30
30
  concurrent-ruby (~> 1.0)
31
31
  method_source (0.9.2)
32
32
  minitest (5.14.4)
data/LICENSE.txt CHANGED
File without changes
data/README.md CHANGED
@@ -16,14 +16,12 @@ Create the config file at `~/.devbox_launcher.yml` so you type less. This is an
16
16
 
17
17
  ```yml
18
18
  ramon@email.com:
19
- project: general-192303
20
- # zone not necessarily required, but sometimes starting the box
21
- # fails without this:
22
- zone: us-central1-a
23
- box: your-instance-name
24
- mutagen:
25
- alpha: /mnt/c/Users/me/src # local machine
26
- beta: ~/src # remote machine
19
+ - box: your-instance-name
20
+ project: general-192303
21
+ zone: us-central1-a
22
+ mutagen:
23
+ alpha: /mnt/c/Users/me/src # local machine
24
+ beta: ~/src # remote machine
27
25
  ramon@company.com:
28
26
  project: development-254604
29
27
  box: ramon
@@ -35,7 +33,13 @@ To start and create the mutagen session:
35
33
  devbox start your-username
36
34
  ```
37
35
 
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.
36
+ - Want to ssh in immediately?
37
+ - Add `--ssh` switch
38
+ - Want to mosh in immediately?
39
+ - Add `--mosh` switch. Mosh needs to be [installed](https://mosh.org/) in your development machine.
40
+ - More than one box with the same Google Cloud account?
41
+ - Pass in the box in your command, via `devbox start user@domain.com/box-name`
42
+ - No need to configure `box:` in the YAML file
39
43
 
40
44
  Note: Linux users that sync mutagen sessions need to install [Watchman](https://facebook.github.io/watchman/).
41
45
 
data/Rakefile CHANGED
File without changes
@@ -9,6 +9,7 @@ module DevboxLauncher
9
9
 
10
10
  desc "start configured box for account", "Start a devbox by account"
11
11
  option :mosh, type: :boolean, desc: "Mosh in"
12
+ option :ssh, type: :boolean, desc: "SSH in"
12
13
 
13
14
  def start(account)
14
15
  Box.new(account, options).start
@@ -0,0 +1,22 @@
1
+ module DevboxLauncher
2
+ class AccountConfig
3
+
4
+ attr_reader :account_name
5
+
6
+ def initialize(account_name, config)
7
+ @account_name = account_name
8
+ @config = config
9
+ end
10
+
11
+ def find_box_config(box_name)
12
+ box_config = @config.find { |c| c["box"] == box_name }
13
+
14
+ if box_config.nil?
15
+ fail "No box config found for #{box_name} under account #{account_name}"
16
+ end
17
+
18
+ BoxConfig.new(box_config)
19
+ end
20
+
21
+ end
22
+ end
@@ -6,21 +6,26 @@ module DevboxLauncher
6
6
  Net::SSH::Disconnect,
7
7
  Errno::ECONNRESET,
8
8
  Errno::ETIMEDOUT,
9
+ Errno::ECONNREFUSED,
9
10
  ]
10
11
  WAIT_BOOT_IN_SECONDS = 10.freeze
11
- MAX_BOOT_RETRIES = 10
12
+ MAX_BOOT_RETRIES = 20
12
13
  DEFAULT_IDENTIFY_FILE_PATH = "~/.ssh/google_compute_engine".freeze
13
14
  SSH_CONFIG_PATH = File.expand_path("~/.ssh/config").freeze
14
15
  CONFIG_PATH = File.expand_path("~/.devbox_launcher.yml").freeze
15
16
  CONFIG = YAML.load_file(CONFIG_PATH).freeze
16
17
 
17
- attr_reader :account, :options
18
+ attr_reader :account_and_box_name, :options
18
19
 
19
- def initialize(account, options)
20
- @account = account
20
+ def initialize(account_and_box_name, options)
21
+ @account_and_box_name = account_and_box_name
21
22
  @options = options
22
23
  end
23
24
 
25
+ def account
26
+ @account ||= @account_and_box_name.split("/")[0]
27
+ end
28
+
24
29
  def start
25
30
  start_stdout, start_stderr, start_status =
26
31
  Open3.capture3(start_cmd)
@@ -31,27 +36,11 @@ module DevboxLauncher
31
36
 
32
37
  reset_mutagen_session
33
38
 
34
- connect_mosh
39
+ connect_mosh || connect_ssh
35
40
  end
36
41
 
37
42
  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(" ")
43
+ cmd_args_for('start')
55
44
  end
56
45
 
57
46
  def connect_mosh
@@ -61,6 +50,13 @@ module DevboxLauncher
61
50
  system(mosh_cmd)
62
51
  end
63
52
 
53
+ def connect_ssh
54
+ return if options[:ssh].nil?
55
+
56
+ ssh_cmd = %Q(ssh #{hostname})
57
+ system(ssh_cmd)
58
+ end
59
+
64
60
  def wait_boot(tries: 1)
65
61
  Net::SSH.start(hostname, username, timeout: WAIT_BOOT_IN_SECONDS) do |ssh|
66
62
  puts "[#{ssh.exec!('date').chomp}] Machine booted"
@@ -84,8 +80,6 @@ module DevboxLauncher
84
80
  def description(reload: false)
85
81
  return @description if !reload && @description
86
82
 
87
- puts "Fetching box's description..."
88
-
89
83
  describe_stdout, describe_stderr, describe_status =
90
84
  Open3.capture3(describe_cmd)
91
85
 
@@ -101,35 +95,21 @@ module DevboxLauncher
101
95
  end
102
96
 
103
97
  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(" ")
98
+ cmd_args_for('describe')
119
99
  end
120
100
 
121
101
  def set_ssh_config!
122
102
  FileUtils.touch(SSH_CONFIG_PATH)
123
- config = ConfigFile.new
103
+ ssh_config = ConfigFile.new
124
104
  args = {
125
105
  "HostName" => description.ip,
126
106
  "User" => username,
127
107
  "IdentityFile" => DEFAULT_IDENTIFY_FILE_PATH,
128
108
  }
129
109
  args.each do |key, value|
130
- config.set(hostname, key, value)
110
+ ssh_config.set(hostname, key, value)
131
111
  end
132
- config.save
112
+ ssh_config.save
133
113
  end
134
114
 
135
115
  def reset_mutagen_session
@@ -194,8 +174,31 @@ module DevboxLauncher
194
174
  watchman.trigger("mutagen sync flush --label-selector=#{label}")
195
175
  end
196
176
 
177
+ def box_name_from_config
178
+ passed_in_box_name = @account_and_box_name.split("/")[1]
179
+
180
+ case account_config.count
181
+ when 0
182
+ fail "You have to specify box configuration"
183
+ when 1
184
+ account_config.first[:box]
185
+ else
186
+ account_config[name]
187
+ end
188
+ end
189
+
197
190
  def name
198
- @name ||= config[:box]
191
+ return @name if @name
192
+ passed_in_box_name = @account_and_box_name.split("/")[1]
193
+
194
+ name = passed_in_box_name.presence || box_name_from_config
195
+
196
+ if name.blank?
197
+ fail "box name must be given either in the CLI or in config. " \
198
+ "See README.md."
199
+ end
200
+
201
+ @name = name
199
202
  end
200
203
 
201
204
  def hostname
@@ -206,19 +209,44 @@ module DevboxLauncher
206
209
  @username ||= account.gsub(/\W/, "_")
207
210
  end
208
211
 
209
- def config
210
- return @config if @config
212
+ def account_config
213
+ return @account_config if @account_config
211
214
 
212
215
  if not CONFIG.has_key?(account)
213
216
  fail "No config in #{CONFIG_PATH} found for #{account}"
214
217
  end
215
218
 
216
- @config = CONFIG[account].with_indifferent_access
219
+ @account_config = AccountConfig.new(account, CONFIG[account])
220
+ end
221
+
222
+ def box_config
223
+ account_config.find_box_config(name)
217
224
  end
218
225
 
219
226
  def mutagen_config
220
- @mutagen_config ||= Mutagen.new(config[:mutagen])
227
+ @mutagen_config ||= box_config.mutagen_config
221
228
  end
222
229
 
230
+ def cmd_args_for(method)
231
+ args = {
232
+ project: box_config.project,
233
+ account: account,
234
+ zone: box_config.zone,
235
+ }.each_with_object([]) do |(key, val), arr|
236
+ next if val.blank?
237
+ arr << ["--#{key}", val].join("=")
238
+ end.join(" ")
239
+
240
+ [
241
+ "gcloud",
242
+ "compute",
243
+ "instances",
244
+ method,
245
+ name,
246
+ args
247
+ ].join(" ")
248
+ end
249
+
250
+
223
251
  end
224
252
  end
@@ -0,0 +1,23 @@
1
+ module DevboxLauncher
2
+ class BoxConfig
3
+
4
+ attr_reader :config
5
+
6
+ def initialize(config)
7
+ @config = config.with_indifferent_access
8
+ end
9
+
10
+ def mutagen_config
11
+ Mutagen.new(config[:mutagen])
12
+ end
13
+
14
+ def project
15
+ config[:project]
16
+ end
17
+
18
+ def zone
19
+ config[:zone]
20
+ end
21
+
22
+ end
23
+ end
@@ -8,7 +8,8 @@ module DevboxLauncher
8
8
  end
9
9
 
10
10
  def configured?
11
- [config, alpha_dir, beta_dir].none?(&:nil?)
11
+ return false if config.nil?
12
+ [alpha_dir, beta_dir].all?(&:present?)
12
13
  end
13
14
 
14
15
  def alpha_dir
@@ -1,3 +1,3 @@
1
1
  module DevboxLauncher
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.1"
3
3
  end
@@ -20,3 +20,5 @@ require "devbox_launcher/watchman"
20
20
  require "devbox_launcher/models/description"
21
21
  require "devbox_launcher/models/mutagen"
22
22
  require "devbox_launcher/models/box"
23
+ require "devbox_launcher/models/account_config"
24
+ require "devbox_launcher/models/box_config"
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.5.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ramon Tayag
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-06 00:00:00.000000000 Z
11
+ date: 2021-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -189,9 +189,10 @@ files:
189
189
  - bin/setup
190
190
  - devbox_launcher.gemspec
191
191
  - lib/devbox_launcher.rb
192
- - lib/devbox_launcher/box.rb
193
192
  - lib/devbox_launcher/cli.rb
193
+ - lib/devbox_launcher/models/account_config.rb
194
194
  - lib/devbox_launcher/models/box.rb
195
+ - lib/devbox_launcher/models/box_config.rb
195
196
  - lib/devbox_launcher/models/description.rb
196
197
  - lib/devbox_launcher/models/mutagen.rb
197
198
  - lib/devbox_launcher/version.rb
@@ -219,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
220
  - !ruby/object:Gem::Version
220
221
  version: '0'
221
222
  requirements: []
222
- rubygems_version: 3.1.4
223
+ rubygems_version: 3.1.6
223
224
  signing_key:
224
225
  specification_version: 4
225
226
  summary: Conveniently launch your devbox
@@ -1,214 +0,0 @@
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