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.
- 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
|