google-cloud-gemserver 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +7 -0
- data/CONTRIBUTING.md +49 -0
- data/LICENSE +202 -0
- data/README.md +179 -0
- data/bin/console +7 -0
- data/bin/google-cloud-gemserver +5 -0
- data/bin/setup +6 -0
- data/lib/google/cloud/gemserver.rb +33 -0
- data/lib/google/cloud/gemserver/authentication.rb +254 -0
- data/lib/google/cloud/gemserver/backend.rb +33 -0
- data/lib/google/cloud/gemserver/backend/gemstash_server.rb +60 -0
- data/lib/google/cloud/gemserver/backend/key.rb +152 -0
- data/lib/google/cloud/gemserver/backend/stats.rb +149 -0
- data/lib/google/cloud/gemserver/backend/storage_sync.rb +186 -0
- data/lib/google/cloud/gemserver/cli.rb +174 -0
- data/lib/google/cloud/gemserver/cli/cloud_sql.rb +196 -0
- data/lib/google/cloud/gemserver/cli/project.rb +143 -0
- data/lib/google/cloud/gemserver/cli/request.rb +130 -0
- data/lib/google/cloud/gemserver/cli/server.rb +337 -0
- data/lib/google/cloud/gemserver/configuration.rb +505 -0
- data/lib/google/cloud/gemserver/gcs.rb +171 -0
- data/lib/google/cloud/gemserver/version.rb +23 -0
- data/lib/patched/configuration.rb +29 -0
- data/lib/patched/dependencies.rb +33 -0
- data/lib/patched/env.rb +42 -0
- data/lib/patched/gem_pusher.rb +28 -0
- data/lib/patched/gem_yanker.rb +28 -0
- data/lib/patched/storage.rb +37 -0
- data/lib/patched/web.rb +64 -0
- metadata +271 -0
@@ -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
|