google-cloud-gemserver 0.1.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.
@@ -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