google-cloud-gemserver 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,337 @@
1
+ # Copyright 2017 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "google/cloud/gemserver"
16
+ require "fileutils"
17
+ require "yaml"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Gemserver
22
+ class CLI
23
+
24
+ ##
25
+ # # Server
26
+ #
27
+ # Object responsible for deploying the gemserver to Google Cloud
28
+ # Platform and starting it.
29
+ #
30
+ class Server
31
+
32
+ ##
33
+ # The Configuration object used to deploy the gemserver
34
+ # @return [Configuration]
35
+ attr_reader :config
36
+
37
+ ##
38
+ # Creates a Server instance by initializing a Configuration object
39
+ # that will be used to access paths to necessary configuration files.
40
+ def initialize
41
+ @config = Configuration.new
42
+ end
43
+
44
+ ##
45
+ # Starts the gemserver by starting up the gemstash server with
46
+ # predefined options.
47
+ def start
48
+ path = if ENV["APP_ENV"] == "production"
49
+ Configuration::GAE_PATH
50
+ else
51
+ @config.config_path
52
+ end
53
+ args = [
54
+ "start",
55
+ "--no-daemonize",
56
+ "--config-file=#{path}"
57
+ ].freeze
58
+ Google::Cloud::Gemserver::Backend::StorageSync.download_service
59
+ Google::Cloud::Gemserver::Backend::GemstashServer.start args
60
+ end
61
+
62
+ ##
63
+ # Deploys the gemserver to Google Cloud Platform if the app
64
+ # environment variable is set to "production." Otherwise, the
65
+ # gemserver is started locally.
66
+ def deploy
67
+ begin
68
+ return start if ["test", "dev"].include? ENV["APP_ENV"]
69
+ deploy_to_gae
70
+ setup_default_keys
71
+ ensure
72
+ cleanup
73
+ end
74
+ end
75
+
76
+ ##
77
+ # Updates the gemserver on a Google Cloud Platform project by
78
+ # redeploying it.
79
+ def update
80
+ return unless Configuration.deployed?
81
+ puts "Updating gemserver..."
82
+ deploy_to_gae
83
+ end
84
+
85
+ ##
86
+ # Deletes a given gemserver by its parent project's ID.
87
+ #
88
+ # @param [String] proj_id The project ID of the project the gemserver
89
+ # was deployed to.
90
+ def delete proj_id
91
+ return unless Configuration.deployed?
92
+ full_delete = user_input("This will delete the entire Google Cloud"\
93
+ " Platform project #{proj_id}. Continue"\
94
+ " deletion? (Y|n, default n) If no, all relevant resources will"\
95
+ " be deleted besides the parent GCP project.").downcase
96
+ if full_delete == "y"
97
+ puts "Deleting gemserver with parent project"
98
+ system "gcloud projects delete #{proj_id}"
99
+ else
100
+ @config.delete_from_cloud
101
+ del_gcs_files
102
+ inst = @config.app["beta_settings"]["cloud_sql_instances"]
103
+ .split(":").pop
104
+ puts "Deleting child Cloud SQL instance #{inst}..."
105
+ params = "delete #{inst} --project #{proj_id}"
106
+ status = system "gcloud beta sql instances #{params}"
107
+ fail "Unable to delete instance" unless status
108
+ puts "The Cloud SQL instance has been deleted. Visit:\n "\
109
+ "https://console.cloud.google.com/appengine/settings?project="\
110
+ "#{proj_id} and click \"Disable Application\" to delete the "\
111
+ "Google App Engine application the gemserver was deployed to."
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ ##
118
+ # Deploys the gemserver to Google App Engine and uploads the
119
+ # configuration file used by the gemserver to Google Cloud Storage
120
+ # for later convenience.
121
+ def deploy_to_gae
122
+ puts "Beginning gemserver deployment..."
123
+ prepare_dir
124
+ path = "#{Configuration::SERVER_PATH}/app.yaml"
125
+ flags = "-q --project #{@config[:proj_id]}"
126
+ status = system "gcloud app deploy #{path} #{flags}"
127
+ fail "Gemserver deployment failed. " unless status
128
+ wait_until_server_accessible
129
+ @config.save_to_cloud
130
+ display_next_steps
131
+ end
132
+
133
+ ##
134
+ # @private Deletes all gem data files on Google Cloud Storage.
135
+ def del_gcs_files
136
+ puts "Deleting all gem data on Google Cloud Storage..."
137
+ gem_files = GCS.files Configuration::GEMSTASH_DIR
138
+ gem_files.each { |f| f.delete }
139
+ end
140
+
141
+ ##
142
+ # @private Creates a key with all permissions and sets it in the
143
+ # necessary configurations (gem credentials and bundle config).
144
+ def setup_default_keys
145
+ should_create = user_input("Would you like to setup a default " \
146
+ "key? [Y/n] (default yes)")
147
+ return if should_create.downcase == "n"
148
+ gemserver_url = remote
149
+ res = Request.new(gemserver_url).create_key
150
+ abort "Error generating key" unless res.code.to_i == 200
151
+ key = Backend::Key.send :parse_key, res.body
152
+ abort "Invalid key" unless valid_key? key
153
+ puts "Generated key: #{key}"
154
+ set_bundle key, gemserver_url
155
+ set_gem_credentials key
156
+ end
157
+
158
+ ##
159
+ # @private Sets a given key in the bundle config used by bundler for
160
+ # installing gems.
161
+ #
162
+ # @param [String] key The key to be added to the bundle config.
163
+ # @param [String] gemserver_url The URL of the gemserver.
164
+ def set_bundle key, gemserver_url
165
+ puts "Updating bundle config"
166
+ run_cmd "bundle config http://#{gemserver_url}/private #{key}"
167
+ end
168
+
169
+ ##
170
+ # @private Sets a given key in the gem credentials file used by
171
+ # Rubygems.org
172
+ #
173
+ # @param [String] key The key to be added to the credentials.
174
+ def set_gem_credentials key
175
+ key_name = sanitize_name(user_input("Updating bundle config. Enter"\
176
+ " a name for your key (default is \"master-gemserver-key\""))
177
+ key_name = key_name.empty? == true ? Configuration::DEFAULT_KEY_NAME : key_name
178
+ puts "Updating #{Configuration::CREDS_PATH}"
179
+
180
+ FileUtils.touch Configuration::CREDS_PATH
181
+ keys = YAML.load_file(Configuration::CREDS_PATH) || {}
182
+
183
+ if keys[key_name.to_sym].nil?
184
+ system "echo \":#{key_name}: #{key}\" >> #{Configuration::CREDS_PATH}"
185
+ else
186
+ puts "The key name \"#{key_name}\" already exists. Please update"\
187
+ " #{Configuration::CREDS_PATH} manually to replace the key or" \
188
+ " manually enter a different name into the file for your key:" \
189
+ " #{key}."
190
+ end
191
+ end
192
+
193
+ ##
194
+ # @private Checks if a key is valid by its length and value.
195
+ #
196
+ # @param [String] key The key to be validated.
197
+ #
198
+ # @return [Boolean]
199
+ def valid_key? key
200
+ size = key.size == Backend::Key::KEY_LENGTH
201
+ m_size = key.gsub(/[^0-9a-z]/i, "").size == Backend::Key::KEY_LENGTH
202
+ size && m_size
203
+ end
204
+
205
+ ##
206
+ # @private Sanitizes a name by removing special symbols and ensuring
207
+ # it is alphanumeric (and hyphens, underscores).
208
+ #
209
+ # @param [String] name The name to be sanitized.
210
+ #
211
+ # @return [String]
212
+ def sanitize_name name
213
+ name = name.chomp
214
+ name.gsub(/[^0-9a-z\-\_]/i, "")
215
+ end
216
+
217
+ ##
218
+ # @private Outputs helpful information to the console indicating the
219
+ # URL the gemserver is running at and how to use the gemserver.
220
+ def display_next_steps
221
+ puts "\nThe gemserver has been deployed! It is running on #{remote}"
222
+ puts "\nTo see the status of the gemserver, visit: \n" \
223
+ " #{remote}/health"
224
+ puts "\nTo see how to use your gemserver to push and download " \
225
+ "gems read https://github.com/GoogleCloudPlatform/google-cloud-" \
226
+ "gemserver/blob/master/docs/usage_example.md for some examples."
227
+ puts "\nFor general information, visit https://github.com/" \
228
+ "GoogleCloudPlatform/google-cloud-gemserver/blob/master/README.md"
229
+ end
230
+
231
+ ##
232
+ # @private Pings the gemserver until a timeout or the gemserver
233
+ # replies with a 200 response code.
234
+ #
235
+ # @param [Integer] timeout The length of time the gemserver is
236
+ # pinged. Optional.
237
+ def wait_until_server_accessible timeout = 60
238
+ puts "Waiting for the gemserver to be accessible..."
239
+ start_time = Time.now
240
+ loop do
241
+ if Time.now - start_time > timeout
242
+ fail "Could not establish a connection to the gemserver"
243
+ else
244
+ r = Request.new(nil, @config[:proj_id]).health
245
+ break if r.code.to_i == 200
246
+ end
247
+ sleep 5
248
+ end
249
+ end
250
+
251
+ ##
252
+ # @private The URL of the gemserver.
253
+ #
254
+ # @return [String]
255
+ def remote
256
+ flag = "--project #{@config[:proj_id]}"
257
+ descrip = YAML.load(run_cmd "gcloud app describe #{flag}")
258
+ descrip["defaultHostname"]
259
+ end
260
+
261
+ ##
262
+ # @private The Gemfile used by the gemserver on Google App Engine.
263
+ #
264
+ # @return [String]
265
+ def gemfile_source
266
+ <<~SOURCE
267
+ source "https://rubygems.org"
268
+
269
+ gem "google-cloud-gemserver", "#{Google::Cloud::Gemserver::VERSION}", path: "."
270
+ gem "concurrent-ruby", require: "concurrent"
271
+ gem "gemstash", git: "https://github.com/bundler/gemstash.git", ref: "a5a78e2"
272
+ gem "mysql2", "~> 0.4"
273
+ gem "filelock", "~> 1.1.1"
274
+ gem "google-cloud-storage", "~> 1.1.0"
275
+ gem "google-cloud-resource_manager", "~> 0.24"
276
+ gem "activesupport", "~> 4.2"
277
+ SOURCE
278
+ end
279
+
280
+ ##
281
+ # @private Creates a Gemfile and Gemfile.lock for the gemserver that
282
+ # runs on Google App Engine such that gemstash is not required
283
+ # client side for the CLI.
284
+ def gemfile
285
+ File.open("#{Configuration::SERVER_PATH}/Gemfile", "w") do |f|
286
+ f.write gemfile_source
287
+ end
288
+
289
+ require "bundler"
290
+ Bundler.with_clean_env do
291
+ run_cmd "cd #{Configuration::SERVER_PATH} && bundle lock"
292
+ end
293
+ end
294
+
295
+ ##
296
+ # @private Creates a temporary directory with the necessary files to
297
+ # deploy the gemserver.
298
+ def prepare_dir
299
+ dir = Gem::Specification.find_by_name(Configuration::GEM_NAME).gem_dir
300
+ cleanup if Dir.exist? Configuration::SERVER_PATH
301
+ FileUtils.mkpath Configuration::SERVER_PATH
302
+ FileUtils.cp_r "#{dir}/.", Configuration::SERVER_PATH
303
+ FileUtils.cp @config.config_path, Configuration::SERVER_PATH
304
+ FileUtils.cp @config.app_path, Configuration::SERVER_PATH
305
+ gemfile
306
+ end
307
+
308
+ ##
309
+ # @private Deletes the temporary directory containing the files used
310
+ # to deploy the gemserver.
311
+ def cleanup
312
+ FileUtils.rm_rf Configuration::SERVER_PATH
313
+ end
314
+
315
+ ##
316
+ # @private Runs a given command on the local machine.
317
+ #
318
+ # @param [String] args The command to be run.
319
+ def run_cmd args
320
+ `#{args}`
321
+ end
322
+
323
+ ##
324
+ # @private Gets input from the user after displaying a message.
325
+ #
326
+ # @param [String] msg The message to be displayed.
327
+ #
328
+ # @return [String]
329
+ def user_input msg
330
+ puts msg
331
+ STDIN.gets.chomp
332
+ end
333
+ end
334
+ end
335
+ end
336
+ end
337
+ end
@@ -0,0 +1,505 @@
1
+ # Copyright 2017 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "fileutils"
16
+ require "yaml"
17
+ require "active_support/core_ext/hash/deep_merge"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Gemserver
22
+ ##
23
+ #
24
+ # # Configuration
25
+ #
26
+ # Stores configurations for the gemserver and provides methods for
27
+ # altering that configuration.
28
+ #
29
+ class Configuration
30
+ ##
31
+ # Default configuration settings for the production database.
32
+ PROD_DB_DEFAULTS = {
33
+ database: "mygems",
34
+ username: "test",
35
+ password: "test",
36
+ host: "localhost",
37
+ socket: "# this will be set automatically"
38
+ }.freeze
39
+
40
+ ##
41
+ # Default configuration settings for the dev database.
42
+ DEV_DB_DEFAULTS = {
43
+ database: "mygems",
44
+ username: "test",
45
+ password: "test",
46
+ host: "localhost",
47
+ socket: "# this will need to be set manually"
48
+ }.freeze
49
+
50
+ ##
51
+ # Default configuration settings for the test database.
52
+ TEST_DB_DEFAULTS = {
53
+ database: "testgems",
54
+ username: "test",
55
+ password: "test",
56
+ host: "localhost",
57
+ }.freeze
58
+
59
+ ##
60
+ # Beta setting used by Google App Engine to connect to the Cloud SQL
61
+ # instance.
62
+ BETA_SETTING_DEFAULTS = {
63
+ "cloud_sql_instances" => "# automatically set"
64
+ }.freeze
65
+
66
+ ##
67
+ # Setting used by Google App Engine to disable health checks for
68
+ # faster deploys.
69
+ HEALTH_CHECK_DEFAULT = {
70
+ "enable_health_check" => false
71
+ }.freeze
72
+
73
+ ##
74
+ # Setting used by Google App Engine to enable auto scaling.
75
+ AUTO_SCALING_DEFAULT = {
76
+ "min_num_instances" => 1,
77
+ "max_num_instances" => 5
78
+ }.freeze
79
+
80
+ ##
81
+ # Default configuration settings for the production gemserver.
82
+ DEFAULT_CONFIG = {
83
+ db_connection_options: PROD_DB_DEFAULTS,
84
+ db_adapter: "cloud_sql",
85
+ cache_type: "memory",
86
+ protected_fetch: true,
87
+ bind: "tcp://0.0.0.0:8080",
88
+ :log_file => :stdout
89
+ }.freeze
90
+
91
+ ##
92
+ # Default configuration settings for the development gemserver.
93
+ DEFAULT_DEV_CONFIG = {
94
+ db_connection_options: DEV_DB_DEFAULTS,
95
+ db_adapter: "cloud_sql",
96
+ cache_type: "memory",
97
+ protected_fetch: true,
98
+ bind: "tcp://0.0.0.0:8080",
99
+ :log_file => :stdout
100
+ }.freeze
101
+
102
+ ##
103
+ # Default configuration settings for the test gemserver.
104
+ DEFAULT_TEST_CONFIG = {
105
+ db_connection_options: TEST_DB_DEFAULTS,
106
+ db_adapter: "sqlite3",
107
+ cache_type: "memory",
108
+ protected_fetch: true,
109
+ bind: "tcp://0.0.0.0:8080",
110
+ :log_file => :stdout
111
+ }.freeze
112
+
113
+ ##
114
+ # Prefix for all general configuration setting fields in app.yaml.
115
+ CONFIG_PREFIX = "gen".freeze
116
+
117
+ ##
118
+ # Prefix for all database configuration setting fields in app.yaml.
119
+ CONFIG_DB_PREFIX = "db".freeze
120
+
121
+ ##
122
+ # Environment variables used by app.yaml for gemserver deployment.
123
+ APP_ENGINE_ENV_VARS = {
124
+ "GEMSERVER_ON_APPENGINE" => true,
125
+ "production_db_database" => "mygems",
126
+ "production_db_username" => "test",
127
+ "production_db_password" => "test",
128
+ "production_db_host" => "localhost",
129
+ "production_db_socket" => "# this is set automatically",
130
+ "production_db_adapter" => "cloud_sql",
131
+ "dev_db_database" => "mygems",
132
+ "dev_db_username" => "test",
133
+ "dev_db_password" => "test",
134
+ "dev_db_host" => "localhost",
135
+ "dev_db_socket" => "# this must be set manually",
136
+ "dev_db_adapter" => "cloud_sql",
137
+ "test_db_database" => "mygems",
138
+ "test_db_username" => "test",
139
+ "test_db_password" => "test",
140
+ "test_db_host" => "localhost",
141
+ "test_db_adapter" => "sqlite3",
142
+ "gen_cache_type" => "memory",
143
+ "gen_protected_fetch" => true,
144
+ "gen_bind" => "tcp://0.0.0.0:8080",
145
+ "gen_log_file" => :stdout
146
+ }.freeze
147
+
148
+ ##
149
+ # Default settings for gemserver deployment to Google App Engine via
150
+ # app.yaml.
151
+ APP_DEFAULTS = {
152
+ "runtime" => "ruby",
153
+ "env" => "flex",
154
+ "entrypoint" => "./bin/google-cloud-gemserver start",
155
+ "beta_settings" => BETA_SETTING_DEFAULTS,
156
+ "health_check" => HEALTH_CHECK_DEFAULT,
157
+ "automatic_scaling" => AUTO_SCALING_DEFAULT,
158
+ "env_variables" => APP_ENGINE_ENV_VARS
159
+ }.freeze
160
+
161
+ ##
162
+ # @private Temporary directory created to prepare a gemserver deploy
163
+ # to a Google Cloud Platform project.
164
+ SERVER_PATH = File.join("/tmp", "google-cloud-gemserver").freeze
165
+
166
+ ##
167
+ # @private Path to the configuration file on Google Cloud Storage that
168
+ # was last used for a gemserver deploy. This path is checked by the
169
+ # `config` command to display the last deployed gemserver's
170
+ # configuration.
171
+ GCS_PATH = "#{SERVER_PATH}/config.yml".freeze
172
+
173
+ ##
174
+ # @private The path to the app folder on a deployed gemserver.
175
+ GAE_DIR = "/app".freeze
176
+
177
+ ##
178
+ # @private The path to the configuration file on a deployed gemserver.
179
+ GAE_PATH = File.join(GAE_DIR, "config.yml").freeze
180
+
181
+ ##
182
+ # @private The path to the credentials file used by the `gem` command.
183
+ CREDS_PATH = File.expand_path(File.join("~", ".gem", "credentials"))
184
+ .freeze
185
+
186
+ ##
187
+ # @private Base directory containing configuration files.
188
+ CONFIG_DIR = File.expand_path(File.join("~", ".google_cloud_gemserver"))
189
+
190
+ ##
191
+ # The name of the gem.
192
+ GEM_NAME = "google-cloud-gemserver".freeze
193
+
194
+ ##
195
+ # @private The default name of the gemserver key.
196
+ DEFAULT_KEY_NAME = "master-gemserver-key".freeze
197
+
198
+ ##
199
+ # @private The path gem data is stored
200
+ GEMSTASH_DIR = "/root/.gemstash".freeze
201
+
202
+ ##
203
+ # The configuration used by the gemserver.
204
+ # @return [Hash]
205
+ attr_accessor :config
206
+
207
+ ##
208
+ # The configuration used by gcloud to deploy the gemserver to Google
209
+ # App Engine.
210
+ # @return [Hash]
211
+ attr_accessor :app
212
+
213
+ ##
214
+ # Instantiate a new instance of Configuration
215
+ def initialize
216
+ @app = load_app
217
+ @config = load_config
218
+ end
219
+
220
+ ##
221
+ # Saves the configuration file used for a deployment.
222
+ def save_to_cloud
223
+ puts "Saving configuration"
224
+ GCS.upload config_path, GCS_PATH
225
+ end
226
+
227
+ ##
228
+ # Deletes the configuration file used for a deployment
229
+ def delete_from_cloud
230
+ GCS.delete_file GCS_PATH
231
+ end
232
+
233
+ ##
234
+ # Updates the configuration file.
235
+ #
236
+ # @param [String] value New value of the key.
237
+ # @param [String] key Name of the key that will be updated.
238
+ # @param [String] sub_key Name of the sub key that will be updated.
239
+ def update_config value, key, sub_key = nil
240
+ if sub_key
241
+ @config[key][sub_key] = value
242
+ else
243
+ @config[key] = value
244
+ end
245
+ write_config
246
+ end
247
+ # Updates the app configuration file.
248
+ #
249
+ # @param [String] value New value of the key.
250
+ # @param [String] key Name of the key that will be updated.
251
+ # @param [String] sub_key Name of the sub key that will be updated.
252
+ def update_app value, key, sub_key = nil
253
+ if sub_key
254
+ @app[key][sub_key] = value
255
+ else
256
+ @app[key] = value
257
+ end
258
+ write_app
259
+ end
260
+
261
+ ##
262
+ # Accesses a key in the Configuration object.
263
+ #
264
+ # @param [String] key Name of the key accessed.
265
+ #
266
+ # @return [String]
267
+ def [] key
268
+ @config[key]
269
+ end
270
+
271
+ ##
272
+ # @private Generates a set of configuration files for the gemserver to
273
+ # run and deploy to Google App Engine.
274
+ def gen_config
275
+ return if on_appengine
276
+ FileUtils.mkpath config_dir unless Dir.exist? config_dir
277
+
278
+ write_file "#{config_dir}/app.yaml", app_config, true
279
+ write_file "#{config_dir}/config.yml", prod_config
280
+ write_file "#{config_dir}/dev_config.yml", dev_config
281
+ write_file "#{config_dir}/test_config.yml", test_config
282
+ end
283
+
284
+ ##
285
+ # Fetches the path to the relevant configuration file based on the
286
+ # environment (production, test, development).
287
+ #
288
+ # @return [String]
289
+ def config_path
290
+ "#{config_dir}/#{suffix}"
291
+ end
292
+
293
+ ##
294
+ # Fetches the path to the relevant app configuration file.
295
+ #
296
+ # @return [String]
297
+ def app_path
298
+ "#{config_dir}/app.yaml"
299
+ end
300
+
301
+ ##
302
+ # Displays the configuration used by the current gemserver
303
+ def self.display_config
304
+ unless deployed?
305
+ puts "No configuration found. Was the gemserver deployed?"
306
+ return
307
+ end
308
+ prepare GCS.get_file(GCS_PATH)
309
+ puts "Gemserver is running with this configuration:"
310
+ puts YAML.load_file(GCS_PATH).to_yaml
311
+ cleanup
312
+ end
313
+
314
+ ##
315
+ # Checks if the gemserver was deployed by the existence of the config
316
+ # file used to deploy it on a specific path on Google Cloud Storage.
317
+ #
318
+ # @return [Boolean]
319
+ def self.deployed?
320
+ !GCS.get_file(GCS_PATH).nil?
321
+ end
322
+
323
+ private
324
+
325
+ ##
326
+ # @private Fetches the current environment.
327
+ #
328
+ # @return [String]
329
+ def env
330
+ ENV["APP_ENV"].nil? == true ? "production" : ENV["APP_ENV"]
331
+ end
332
+
333
+ ##
334
+ # @private Determines which configuration file to read based on the
335
+ # environment.
336
+ #
337
+ # @return [String]
338
+ def suffix
339
+ if env == "dev"
340
+ "dev_config.yml"
341
+ elsif env == "test"
342
+ "test_config.yml"
343
+ else
344
+ "config.yml"
345
+ end
346
+ end
347
+
348
+ ##
349
+ # @private Writes a given file to a given path.
350
+ #
351
+ # @param [String] path The path to write the file.
352
+ #
353
+ # @param [String] content The content to be written to the file.
354
+ #
355
+ # @param [boolean] check_existence If true, the file is not overwritten
356
+ # if it already exists. Optional.
357
+ def write_file path, content, check_existence = false
358
+ if check_existence
359
+ return if File.exist? path
360
+ end
361
+ File.open(path, "w") do |f|
362
+ f.write content
363
+ end
364
+ end
365
+
366
+ ##
367
+ # @private The default app.yaml configuration formatted in YAML.
368
+ #
369
+ # @return [String]
370
+ def app_config
371
+ APP_DEFAULTS.merge(load_app).to_yaml
372
+ end
373
+
374
+ ##
375
+ # @private The default config.yml configuration formatted in YAML
376
+ # used by the gemserver in the production environment.
377
+ #
378
+ # @return [String]
379
+ def prod_config
380
+ DEFAULT_CONFIG.deep_merge(extract_config("production")).to_yaml
381
+ end
382
+
383
+ ##
384
+ # @private The default dev_config.yml configuration formatted in YAML
385
+ # used by the gemserver in the dev environment.
386
+ #
387
+ # @return [String]
388
+ def dev_config
389
+ DEFAULT_DEV_CONFIG.deep_merge(extract_config("dev")).to_yaml
390
+ end
391
+
392
+ ##
393
+ # @private The default test_config.yml configuration formatted in YAML
394
+ # used by the gemserver in the test environment.
395
+ #
396
+ # @return [String]
397
+ def test_config
398
+ DEFAULT_TEST_CONFIG.deep_merge(extract_config("test")).to_yaml
399
+ end
400
+
401
+ ##
402
+ # @private Extracts the gemserver configuration from the app.yaml
403
+ # environment variables.
404
+ #
405
+ # @param [String] pre The prefix of the config fields to extract.
406
+ #
407
+ # @return [Hash]
408
+ def extract_config pre = "production"
409
+ adapter = pre + "_" + CONFIG_DB_PREFIX + "_adapter"
410
+ db_config = @app["env_variables"].map do |k, v|
411
+ # db_adapter is a special case b/c it has the 'db' in its name but is
412
+ # not a db_connection_options field
413
+ next unless k.include?(pre) && k != adapter
414
+ [(k[pre.size + CONFIG_DB_PREFIX.size + 2..k.size - 1]).to_sym, v]
415
+ end.compact.to_h
416
+ config = @app["env_variables"].map do |k, v|
417
+ next unless k.include? CONFIG_PREFIX
418
+ [(k[CONFIG_PREFIX.size + 1..k.size - 1]).to_sym, v]
419
+ end.compact.to_h
420
+ {
421
+ :db_connection_options => db_config,
422
+ :db_adapter => @app["env_variables"][adapter]
423
+ }.deep_merge config
424
+ end
425
+
426
+ ##
427
+ # @private Loads a configuration file.
428
+ #
429
+ # @return [Hash]
430
+ def load_config
431
+ extract_config env
432
+ end
433
+
434
+ ##
435
+ # @private Loads the app configuration file.
436
+ #
437
+ # @return [Hash]
438
+ def load_app
439
+ return APP_DEFAULTS unless File.exist? app_path
440
+ YAML.load_file app_path
441
+ end
442
+
443
+ ##
444
+ # @private Writes the current Configuration object in YAML format
445
+ # to the relevant configuration file (based on environment) and
446
+ # updates app.yaml accordingly.
447
+ def write_config
448
+ db_key = env + "_" + CONFIG_DB_PREFIX + "_"
449
+ key = CONFIG_PREFIX + "_"
450
+ db = @config[:db_connection_options]
451
+ non_db = @config.reject { |k, v| k == :db_connection_options }
452
+ formatted_db = db.map { |k, v| [db_key + k.to_s, v] }.to_h
453
+ formatted_non_db = non_db.map { |k, v| [key + k.to_s, v] }.to_h
454
+ @app["env_variables"] = @app["env_variables"].merge(
455
+ formatted_db.merge(formatted_non_db)
456
+ )
457
+ File.open(config_path, "w") { |f| YAML.dump @config, f }
458
+ write_app
459
+ end
460
+
461
+ ##
462
+ # @private Writes the current app configuration object in YAML format
463
+ # to the app configuration file.
464
+ def write_app
465
+ File.open(app_path, "w") { |f| YAML.dump @app, f }
466
+ end
467
+
468
+ ##
469
+ # @private Fetches the directory that contains the configuration files.
470
+ #
471
+ # @return [String]
472
+ def config_dir
473
+ return GAE_DIR if on_appengine
474
+ dir = ENV["GEMSERVER_CONFIG_DIR"]
475
+ dir.nil? == true ? CONFIG_DIR : dir
476
+ end
477
+
478
+ ##
479
+ # @private Determines if the gemserver is running on Google App Engine.
480
+ #
481
+ # @return [boolean]
482
+ def on_appengine
483
+ !ENV["GEMSERVER_ON_APPENGINE"].nil?
484
+ end
485
+
486
+ ##
487
+ # @private Creates a temporary directory to download the configuration
488
+ # file used to deploy the gemserver.
489
+ def self.prepare file
490
+ FileUtils.mkpath SERVER_PATH
491
+ file.download file.name
492
+ end
493
+
494
+ ##
495
+ # @private Deletes a temporary directory.
496
+ def self.cleanup
497
+ FileUtils.rm_rf SERVER_PATH
498
+ end
499
+
500
+ private_class_method :prepare
501
+ private_class_method :cleanup
502
+ end
503
+ end
504
+ end
505
+ end