devbox_launcher 0.5.1 → 0.7.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: 0d2b9f957cb18b171f3d4acc9d802576b1bd768eb6afa6036ae23143b9403ab1
4
- data.tar.gz: f0dc90ba120f6d34f42b0432b53728bd9caf40468262091dd2c88e15bdc16fcf
3
+ metadata.gz: c247836992ba6c6f8b7dc733aca134394ea41eaf0ccf1026e72837992d77aa57
4
+ data.tar.gz: 7fde1e5aeff66e1b199d9b1bac17d80368c50d90eb74cd7ddf29fa9923122886
5
5
  SHA512:
6
- metadata.gz: ba367cf32efeef9ac13b46c9c3af638b1998ff7c335a458c4c4421e0c849a043e44fb97d904b69428936393fdc1754adef71ead9197c46d96beacb25e6bee909
7
- data.tar.gz: 5b7b7216addc48d9982e9706069a7ecf293e5543f3d11c4aff7997882b2f274ab0fe3bbf6b98b1833008cc9354b8753c9e3591756e9df7ad82c949bd353e1cb4
6
+ metadata.gz: 5bb774576f0003c53c757751b3d502cfedee2f4ef685a0e15031265f80f8d70bcf106f1cd752492f496f1ccce42068d8bceb33219de2b833b0022b1aae348bc2
7
+ data.tar.gz: d8fe891e797ba651ee5591ee309d6ab51045179746af5b57c88d75e32773321accb2ae60332bf1bc79a4028f2347f3f49493daabf4b9edba176211f69ee7ded8
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.7.0] - 2021-09-23
8
+ ### Added
9
+ - Add ability to set a box's user and identity_file
10
+
11
+ ## [0.6.1] - 2021-09-22
12
+ ### Fixed
13
+ - Do not blow up if mutagen is not configured
14
+
15
+ ## [0.6.0] - 2021-09-21
16
+ ### Changed
17
+ - Ability to configure multiple boxes under one account
18
+
19
+ ## [0.5.2] - 2021-05-31
20
+ ### Added
21
+ - Ignore VCS as recommended by mutagen
22
+
23
+ ### Fixed
24
+ - Retry on `Errno::ECONNREFUSED`
25
+
7
26
  ## [0.5.1] - 2021-04-08
8
27
  ### Fixed
9
28
  - Use configured `zone` for describe as well
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.1)
4
+ devbox_launcher (0.7.0)
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,17 +16,17 @@ 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
- project: development-254604
29
- box: ramon
26
+ - project: development-254604
27
+ box: ramon
28
+ user: another_user # only needed if ramon_company_com is not the user
29
+ identity_file: /path/to/ssh-private # ~/.ssh/google_compute_engine by default
30
30
  ```
31
31
 
32
32
  To start and create the mutagen session:
@@ -35,7 +35,13 @@ To start and create the mutagen session:
35
35
  devbox start your-username
36
36
  ```
37
37
 
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.
38
+ - Want to ssh in immediately?
39
+ - Add `--ssh` switch
40
+ - Want to mosh in immediately?
41
+ - Add `--mosh` switch. Mosh needs to be [installed](https://mosh.org/) in your development machine.
42
+ - More than one box with the same Google Cloud account?
43
+ - Pass in the box in your command, via `devbox start user@domain.com/box-name`
44
+ - No need to configure `box:` in the YAML file
39
45
 
40
46
  Note: Linux users that sync mutagen sessions need to install [Watchman](https://facebook.github.io/watchman/).
41
47
 
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,25 @@ 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
- DEFAULT_IDENTIFY_FILE_PATH = "~/.ssh/google_compute_engine".freeze
12
+ MAX_BOOT_RETRIES = 20
13
13
  SSH_CONFIG_PATH = File.expand_path("~/.ssh/config").freeze
14
14
  CONFIG_PATH = File.expand_path("~/.devbox_launcher.yml").freeze
15
15
  CONFIG = YAML.load_file(CONFIG_PATH).freeze
16
16
 
17
- attr_reader :account, :options
17
+ attr_reader :account_and_box_name, :options
18
18
 
19
- def initialize(account, options)
20
- @account = account
19
+ def initialize(account_and_box_name, options)
20
+ @account_and_box_name = account_and_box_name
21
21
  @options = options
22
22
  end
23
23
 
24
+ def account
25
+ @account ||= @account_and_box_name.split("/")[0]
26
+ end
27
+
24
28
  def start
25
29
  start_stdout, start_stderr, start_status =
26
30
  Open3.capture3(start_cmd)
@@ -31,7 +35,7 @@ module DevboxLauncher
31
35
 
32
36
  reset_mutagen_session
33
37
 
34
- connect_mosh
38
+ connect_mosh || connect_ssh
35
39
  end
36
40
 
37
41
  def start_cmd
@@ -45,6 +49,13 @@ module DevboxLauncher
45
49
  system(mosh_cmd)
46
50
  end
47
51
 
52
+ def connect_ssh
53
+ return if options[:ssh].nil?
54
+
55
+ ssh_cmd = %Q(ssh #{hostname})
56
+ system(ssh_cmd)
57
+ end
58
+
48
59
  def wait_boot(tries: 1)
49
60
  Net::SSH.start(hostname, username, timeout: WAIT_BOOT_IN_SECONDS) do |ssh|
50
61
  puts "[#{ssh.exec!('date').chomp}] Machine booted"
@@ -68,8 +79,6 @@ module DevboxLauncher
68
79
  def description(reload: false)
69
80
  return @description if !reload && @description
70
81
 
71
- puts "Fetching box's description..."
72
-
73
82
  describe_stdout, describe_stderr, describe_status =
74
83
  Open3.capture3(describe_cmd)
75
84
 
@@ -90,16 +99,16 @@ module DevboxLauncher
90
99
 
91
100
  def set_ssh_config!
92
101
  FileUtils.touch(SSH_CONFIG_PATH)
93
- config = ConfigFile.new
102
+ ssh_config = ConfigFile.new
94
103
  args = {
95
104
  "HostName" => description.ip,
96
105
  "User" => username,
97
- "IdentityFile" => DEFAULT_IDENTIFY_FILE_PATH,
106
+ "IdentityFile" => box_config.identity_file,
98
107
  }
99
108
  args.each do |key, value|
100
- config.set(hostname, key, value)
109
+ ssh_config.set(hostname, key, value)
101
110
  end
102
- config.save
111
+ ssh_config.save
103
112
  end
104
113
 
105
114
  def reset_mutagen_session
@@ -164,8 +173,31 @@ module DevboxLauncher
164
173
  watchman.trigger("mutagen sync flush --label-selector=#{label}")
165
174
  end
166
175
 
176
+ def box_name_from_config
177
+ passed_in_box_name = @account_and_box_name.split("/")[1]
178
+
179
+ case account_config.count
180
+ when 0
181
+ fail "You have to specify box configuration"
182
+ when 1
183
+ account_config.first[:box]
184
+ else
185
+ account_config[name]
186
+ end
187
+ end
188
+
167
189
  def name
168
- @name ||= config[:box]
190
+ return @name if @name
191
+ passed_in_box_name = @account_and_box_name.split("/")[1]
192
+
193
+ name = passed_in_box_name.presence || box_name_from_config
194
+
195
+ if name.blank?
196
+ fail "box name must be given either in the CLI or in config. " \
197
+ "See README.md."
198
+ end
199
+
200
+ @name = name
169
201
  end
170
202
 
171
203
  def hostname
@@ -173,28 +205,32 @@ module DevboxLauncher
173
205
  end
174
206
 
175
207
  def username
176
- @username ||= account.gsub(/\W/, "_")
208
+ @username ||= box_config.user || account.gsub(/\W/, "_")
177
209
  end
178
210
 
179
- def config
180
- return @config if @config
211
+ def account_config
212
+ return @account_config if @account_config
181
213
 
182
214
  if not CONFIG.has_key?(account)
183
215
  fail "No config in #{CONFIG_PATH} found for #{account}"
184
216
  end
185
217
 
186
- @config = CONFIG[account].with_indifferent_access
218
+ @account_config = AccountConfig.new(account, CONFIG[account])
219
+ end
220
+
221
+ def box_config
222
+ account_config.find_box_config(name)
187
223
  end
188
224
 
189
225
  def mutagen_config
190
- @mutagen_config ||= Mutagen.new(config[:mutagen])
226
+ @mutagen_config ||= box_config.mutagen_config
191
227
  end
192
228
 
193
229
  def cmd_args_for(method)
194
230
  args = {
195
- project: config[:project],
231
+ project: box_config.project,
196
232
  account: account,
197
- zone: config[:zone],
233
+ zone: box_config.zone,
198
234
  }.each_with_object([]) do |(key, val), arr|
199
235
  next if val.blank?
200
236
  arr << ["--#{key}", val].join("=")
@@ -0,0 +1,33 @@
1
+ module DevboxLauncher
2
+ class BoxConfig
3
+
4
+ DEFAULT_IDENTIFY_FILE_PATH = "~/.ssh/google_compute_engine".freeze
5
+
6
+ attr_reader :config
7
+
8
+ def initialize(config)
9
+ @config = config.with_indifferent_access
10
+ end
11
+
12
+ def mutagen_config
13
+ Mutagen.new(config[:mutagen])
14
+ end
15
+
16
+ def project
17
+ config[:project]
18
+ end
19
+
20
+ def zone
21
+ config[:zone]
22
+ end
23
+
24
+ def user
25
+ config[:user]
26
+ end
27
+
28
+ def identity_file
29
+ config[:identity_file] || DEFAULT_IDENTIFY_FILE_PATH
30
+ end
31
+
32
+ end
33
+ 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.1"
2
+ VERSION = "0.7.0"
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.1
4
+ version: 0.7.0
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-08 00:00:00.000000000 Z
11
+ date: 2021-09-23 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