appengine 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +0 -1
- data/lib/appengine/exec.rb +7 -835
- data/lib/appengine/tasks.rb +1 -1
- data/lib/appengine/version.rb +1 -1
- metadata +32 -33
- data/data/exec_standard_entrypoint.rb.erb +0 -116
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 316bcfd3b1413ddbb299da04099876fc58f2b38f91bef06d6f9d7d23de0b2a7d
|
4
|
+
data.tar.gz: 787086e724920361bf91caa2a8320a84d5b9e794c15175bb70dec0ab1337d30d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81bc955696cda3b5042fa75b3c1e14b99724721a49e262fd178e9ec0c6c99442ab3c7598f484cb289a651f93eb4dfd61e6b811a4857a41e6da8d56290f7f4071
|
7
|
+
data.tar.gz: 52fe0dd05a899f5f9c09929e5dcf245cc4a64cbdd72b8a1e9adee0087928efe1d11b14379a1bdab549c47b025ddae9cab62cb383fc4bfa343321b5d891d8a53a
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,18 @@
|
|
2
2
|
|
3
3
|
This is the change history for the appengine gem.
|
4
4
|
|
5
|
+
### 0.7.0 (2022-04-22)
|
6
|
+
|
7
|
+
#### Features
|
8
|
+
|
9
|
+
* add gcs log dir option
|
10
|
+
* Replace the appengine exec class with an alias to serverless-exec
|
11
|
+
* Update stackdriver dependency
|
12
|
+
#### Bug Fixes
|
13
|
+
|
14
|
+
* Fix exception when a shell command rather than a command array is given in Exec
|
15
|
+
* Fix failure in the appengine:exec cloud_build strategy when App Engine doesn't provide the image
|
16
|
+
|
5
17
|
## v0.6.0 (2020-12-02)
|
6
18
|
|
7
19
|
* Fix failure in the appengine:exec cloud_build strategy when App Engine doesn't provide the image
|
data/README.md
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
Google App Engine Integration Tools
|
2
2
|
===================================
|
3
3
|
|
4
|
-
[](https://circleci.com/gh/GoogleCloudPlatform/appengine-ruby)
|
5
4
|
[](https://badge.fury.io/rb/appengine)
|
6
5
|
|
7
6
|
This repository contains the "appengine" gem, a collection of libraries and
|
data/lib/appengine/exec.rb
CHANGED
@@ -15,843 +15,15 @@
|
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
17
|
|
18
|
-
require "
|
19
|
-
require "erb"
|
20
|
-
require "json"
|
21
|
-
require "net/http"
|
22
|
-
require "securerandom"
|
23
|
-
require "shellwords"
|
24
|
-
require "tempfile"
|
25
|
-
require "yaml"
|
26
|
-
|
27
|
-
require "appengine/util/gcloud"
|
18
|
+
require "google/serverless/exec"
|
28
19
|
|
29
20
|
module AppEngine
|
30
|
-
##
|
31
|
-
# # App Engine remote execution
|
32
|
-
#
|
33
|
-
# This class provides a client for App Engine remote execution, allowing
|
34
|
-
# App Engine applications to perform on-demand tasks in the App Engine
|
35
|
-
# environment. This may be used for safe running of ops and maintenance
|
36
|
-
# tasks, such as database migrations, that access production cloud resources.
|
37
|
-
#
|
38
|
-
# ## About App Engine execution
|
39
|
-
#
|
40
|
-
# App Engine execution spins up a one-off copy of an App Engine app, and runs
|
41
|
-
# a command against it. For example, if your app runs on Ruby on Rails, then
|
42
|
-
# you might use App Engine execution to run a command such as
|
43
|
-
# `bundle exec bin/rails db:migrate` in production infrastructure (to avoid
|
44
|
-
# having to connect directly to your production database from a local
|
45
|
-
# workstation).
|
46
|
-
#
|
47
|
-
# App Engine execution provides two strategies for generating that "one-off
|
48
|
-
# copy":
|
49
|
-
#
|
50
|
-
# * A `deployment` strategy, which deploys a temporary version of your app
|
51
|
-
# to a single backend instance and runs the command there.
|
52
|
-
# * A `cloud_build` strategy, which deploys your application image to
|
53
|
-
# Google Cloud Build and runs the command there.
|
54
|
-
#
|
55
|
-
# Both strategies are generally designed to emulate the App Engine runtime
|
56
|
-
# environment on cloud VMs similar to those used by actual deployments of
|
57
|
-
# your app. Both provide your application code and environment variables, and
|
58
|
-
# both provide access to Cloud SQL connections used by your app. However,
|
59
|
-
# they differ in what *version* of your app code they run against, and in
|
60
|
-
# certain other constraints and performance characteristics. More detailed
|
61
|
-
# information on using the two strategies is provided in the sections below.
|
62
|
-
#
|
63
|
-
# Apps deployed to the App Engine *flexible environment* will use the
|
64
|
-
# `cloud_build` strategy by default. However, you can force an app to use the
|
65
|
-
# `deployment` strategy instead. (You might do so if you need to connect to a
|
66
|
-
# Cloud SQL database on a VPC using a private IP, because the `cloud_build`
|
67
|
-
# strategy does not support private IPs.) To force use of `deployment`, set
|
68
|
-
# the `strategy` parameter in the {AppEngine::Exec} constructor (or the
|
69
|
-
# corresponding `GAE_EXEC_STRATEGY` parameter in the Rake task). Note that
|
70
|
-
# the `deployment` strategy is usually significantly slower than
|
71
|
-
# `cloud_build` for apps in the flexible environment.
|
72
|
-
#
|
73
|
-
# Apps deployed to the App Engine *standard environment* will *always* use
|
74
|
-
# the `deployment` strategy. You cannot force use of the `cloud_build`
|
75
|
-
# strategy.
|
76
|
-
#
|
77
|
-
# ## Prerequisites
|
78
|
-
#
|
79
|
-
# To use App Engine remote execution, you will need:
|
80
|
-
#
|
81
|
-
# * An app deployed to Google App Engine, of course!
|
82
|
-
# * The [gcloud SDK](https://cloud.google.com/sdk/) installed and configured.
|
83
|
-
# * The `appengine` gem.
|
84
|
-
#
|
85
|
-
# You may use the `AppEngine::Exec` class to run commands directly. However,
|
86
|
-
# in most cases, it will be easier to run commands via the provided rake
|
87
|
-
# tasks (see {AppEngine::Tasks}).
|
88
|
-
#
|
89
|
-
# ## Using the "deployment" strategy
|
90
|
-
#
|
91
|
-
# The `deployment` strategy deploys a temporary version of your app to a
|
92
|
-
# single backend App Engine instance, runs the command there, and then
|
93
|
-
# deletes the temporary version when it is finished.
|
94
|
-
#
|
95
|
-
# This is the default strategy (and indeed the only option) for apps running
|
96
|
-
# on the App Engine standard environment. It can also be used for flexible
|
97
|
-
# environment apps, but this is not commonly done because deployment of
|
98
|
-
# flexible environment apps can take a long time.
|
99
|
-
#
|
100
|
-
# Because the `deployment` strategy deploys a temporary version of your app,
|
101
|
-
# it runs against the *current application code* present where the command
|
102
|
-
# was initiated (i.e. the code currently on your workstation if you run the
|
103
|
-
# rake task from your workstation, or the current code on the branch if you
|
104
|
-
# are running from a CI/CD system.) This may be different from the code
|
105
|
-
# actually running in production, so it is important that you run from a
|
106
|
-
# compatible code branch.
|
107
|
-
#
|
108
|
-
# ### Specifying the host application
|
109
|
-
#
|
110
|
-
# The `deployment` strategy works by deploying a temporary version of your
|
111
|
-
# app, so that it has access to your app's project and settings in App
|
112
|
-
# Engine. In most cases, it can determine this automatically, but depending
|
113
|
-
# on how your app or environment is structured, you may need to give it some
|
114
|
-
# help.
|
115
|
-
#
|
116
|
-
# By default, your Google Cloud project is taken from the current gcloud
|
117
|
-
# project. If you need to override this, set the `:project` parameter in the
|
118
|
-
# {AppEngine::Exec} constructor (or the corresponding `GAE_PROJECT`
|
119
|
-
# parameter in the Rake task).
|
120
|
-
#
|
121
|
-
# By default, the service name is taken from the App Engine config file.
|
122
|
-
# App Engine execution will assume this file is called `app.yaml` in the
|
123
|
-
# current directory. To use a different config file, set the `config_path`
|
124
|
-
# parameter in the {AppEngine::Exec} constructor (or the corresponding
|
125
|
-
# `GAE_CONFIG` parameter in the Rake task). You may also set the service name
|
126
|
-
# directly, using the `service` parameter (or `GAE_SERVICE` in Rake).
|
127
|
-
#
|
128
|
-
# ### Providing credentials
|
129
|
-
#
|
130
|
-
# Your command will effectively be a deployment of your App Engine app
|
131
|
-
# itself, and will have access to the same credentials. For example, App
|
132
|
-
# Engine provides a service account by default for your app, or your app may
|
133
|
-
# be making use of its own service account key. In either case, make sure the
|
134
|
-
# service account has sufficient access for the command you want to run
|
135
|
-
# (such as database admin credentials).
|
136
|
-
#
|
137
|
-
# ### Other options
|
138
|
-
#
|
139
|
-
# You may also provide a timeout, which is the length of time that App
|
140
|
-
# Engine execution will allow your command to run before it is considered to
|
141
|
-
# have stalled and is terminated. The timeout should be a string of the form
|
142
|
-
# `2h15m10s`. The default is `10m`.
|
143
|
-
#
|
144
|
-
# The timeout is set via the `timeout` parameter to the {AppEngine::Exec}
|
145
|
-
# constructor, or by setting the `GAE_TIMEOUT` environment variable when
|
146
|
-
# invoking using Rake.
|
147
|
-
#
|
148
|
-
# ### Resource usage and billing
|
149
|
-
#
|
150
|
-
# The `deployment` strategy deploys to a temporary instance of your app in
|
151
|
-
# order to run the command. You may be billed for that usage. However, the
|
152
|
-
# cost should be minimal, because it will then immediately delete that
|
153
|
-
# instance in order to minimize usage.
|
154
|
-
#
|
155
|
-
# If you interrupt the execution (or it crashes), it is possible that the
|
156
|
-
# temporary instance may not get deleted properly. If you suspect this may
|
157
|
-
# have happened, go to the App Engine tab in the cloud console, under
|
158
|
-
# "versions" of your service, and delete the temporary version manually. It
|
159
|
-
# will have a name matching the pattern `appengine-exec-<timestamp>`.
|
160
|
-
#
|
161
|
-
# ## Using the "cloud_build" strategy
|
162
|
-
#
|
163
|
-
# The `cloud_build` strategy takes the application image that App Engine is
|
164
|
-
# actually using to run your app, and uses it to spin up a copy of your app
|
165
|
-
# in [Google Cloud Build](https://cloud.google.com/cloud-build) (along with
|
166
|
-
# an emulation layer that emulates certain App Engine services such as Cloud
|
167
|
-
# SQL connection sockets). The command then gets run in the Cloud Build
|
168
|
-
# environment.
|
169
|
-
#
|
170
|
-
# This is the default strategy for apps running on the App Engine flexible
|
171
|
-
# environment. (It is not available for standard environment apps.) Note that
|
172
|
-
# the `cloud_build` strategy cannot be used if your command needs to connect
|
173
|
-
# to a database over a [VPC](https://cloud.google.com/vpc/) private IP
|
174
|
-
# address. This is because it runs on virtual machines provided by the Cloud
|
175
|
-
# Build service, which are not part of your VPC. If your database can be
|
176
|
-
# accessed only over a private IP, you should use the `deployment` strategy
|
177
|
-
# instead.
|
178
|
-
#
|
179
|
-
# The Cloud Build log is output to the directory specified by
|
180
|
-
# "CLOUD_BUILD_GCS_LOG_DIR". (ex. "gs://BUCKET-NAME/FOLDER-NAME")
|
181
|
-
# By default, log directory name is
|
182
|
-
# "gs://[PROJECT_NUMBER].cloudbuild-logs.googleusercontent.com/".
|
183
|
-
#
|
184
|
-
# ### Specifying the host application
|
185
|
-
#
|
186
|
-
# The `cloud_build` strategy needs to know exactly which app, service, and
|
187
|
-
# version of your app, to identify the application image to use.
|
188
|
-
#
|
189
|
-
# By default, your Google Cloud project is taken from the current gcloud
|
190
|
-
# project. If you need to override this, set the `:project` parameter in the
|
191
|
-
# {AppEngine::Exec} constructor (or the corresponding `GAE_PROJECT`
|
192
|
-
# parameter in the Rake task).
|
193
|
-
#
|
194
|
-
# By default, the service name is taken from the App Engine config file.
|
195
|
-
# App Engine execution will assume this file is called `app.yaml` in the
|
196
|
-
# current directory. To use a different config file, set the `config_path`
|
197
|
-
# parameter in the {AppEngine::Exec} constructor (or the corresponding
|
198
|
-
# `GAE_CONFIG` parameter in the Rake task). You may also set the service name
|
199
|
-
# directly, using the `service` parameter (or `GAE_SERVICE` in Rake).
|
200
|
-
#
|
201
|
-
# By default, the image of the most recently deployed version of your app is
|
202
|
-
# used. (Note that this most recently deployed version may not be the same
|
203
|
-
# version that is currently receiving traffic: for example, if you deployed
|
204
|
-
# with `--no-promote`.) To use a different version, set the `version`
|
205
|
-
# parameter in the {AppEngine::Exec} constructor (or the corresponding
|
206
|
-
# `GAE_VERSION` parameter in the Rake task).
|
207
|
-
#
|
208
|
-
# ### Providing credentials
|
209
|
-
#
|
210
|
-
# By default, the `cloud_build` strategy uses your project's Cloud Build
|
211
|
-
# service account for its credentials. Unless your command provides its own
|
212
|
-
# service account key, you may need to grant the Cloud Build service account
|
213
|
-
# any permissions needed to execute your command (such as access to your
|
214
|
-
# database). For most tasks, it is sufficient to grant Project Editor
|
215
|
-
# permissions to the service account. You can find the service account
|
216
|
-
# configuration in the IAM tab in the Cloud Console under the name
|
217
|
-
# `[your-project-number]@cloudbuild.gserviceaccount.com`.
|
218
|
-
#
|
219
|
-
# ### Other options
|
220
|
-
#
|
221
|
-
# You may also provide a timeout, which is the length of time that App
|
222
|
-
# Engine execution will allow your command to run before it is considered to
|
223
|
-
# have stalled and is terminated. The timeout should be a string of the form
|
224
|
-
# `2h15m10s`. The default is `10m`.
|
225
|
-
#
|
226
|
-
# The timeout is set via the `timeout` parameter to the {AppEngine::Exec}
|
227
|
-
# constructor, or by setting the `GAE_TIMEOUT` environment variable when
|
228
|
-
# invoking using Rake.
|
229
|
-
#
|
230
|
-
# You can also set the wrapper image used to emulate the App Engine runtime
|
231
|
-
# environment, by setting the `wrapper_image` parameter to the constructor,
|
232
|
-
# or by setting the `GAE_EXEC_WRAPPER_IMAGE` environment variable. Generally,
|
233
|
-
# you will not need to do this unless you are testing a new wrapper image.
|
234
|
-
#
|
235
|
-
# ### Resource usage and billing
|
236
|
-
#
|
237
|
-
# The `cloud_build` strategy uses virtual machine resources provided by
|
238
|
-
# Google Cloud Build. Generally, a certain number of usage minutes per day is
|
239
|
-
# covered under a free tier, but additional compute usage beyond that time is
|
240
|
-
# billed to your Google Cloud account. For more details,
|
241
|
-
# see https://cloud.google.com/cloud-build/pricing
|
242
|
-
#
|
243
|
-
# If your command makes API calls or utilizes other cloud resources, you may
|
244
|
-
# also be billed for that usage. However, the `cloud_build` strategy (unlike
|
245
|
-
# the `deployment` strategy) does not use actual App Engine instances, and
|
246
|
-
# you will not be billed for additional App Engine instance usage.
|
247
21
|
#
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
##
|
255
|
-
# Base class for exec-related usage errors.
|
256
|
-
#
|
257
|
-
class UsageError < ::StandardError
|
258
|
-
end
|
259
|
-
|
260
|
-
##
|
261
|
-
# Unsupported strategy
|
262
|
-
#
|
263
|
-
class UnsupportedStrategy < UsageError
|
264
|
-
def initialize strategy, app_env
|
265
|
-
@strategy = strategy
|
266
|
-
@app_env = app_env
|
267
|
-
super "Strategy \"#{strategy}\" not supported for the #{app_env}" \
|
268
|
-
" environment"
|
269
|
-
end
|
270
|
-
attr_reader :strategy
|
271
|
-
attr_reader :app_env
|
272
|
-
end
|
273
|
-
|
274
|
-
##
|
275
|
-
# Exception raised when a parameter is malformed.
|
276
|
-
#
|
277
|
-
class BadParameter < UsageError
|
278
|
-
def initialize param, value
|
279
|
-
@param_name = param
|
280
|
-
@value = value
|
281
|
-
super "Bad value for #{param}: #{value}"
|
282
|
-
end
|
283
|
-
attr_reader :param_name
|
284
|
-
attr_reader :value
|
285
|
-
end
|
286
|
-
|
287
|
-
##
|
288
|
-
# Exception raised when gcloud has no default project.
|
289
|
-
#
|
290
|
-
class NoDefaultProject < UsageError
|
291
|
-
def initialize
|
292
|
-
super "No default project set."
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
##
|
297
|
-
# Exception raised when the App Engine config file could not be found.
|
298
|
-
#
|
299
|
-
class ConfigFileNotFound < UsageError
|
300
|
-
def initialize config_path
|
301
|
-
@config_path = config_path
|
302
|
-
super "Config file #{config_path} not found."
|
303
|
-
end
|
304
|
-
attr_reader :config_path
|
305
|
-
end
|
306
|
-
|
307
|
-
##
|
308
|
-
# Exception raised when the App Engine config file could not be parsed.
|
309
|
-
#
|
310
|
-
class BadConfigFileFormat < UsageError
|
311
|
-
def initialize config_path
|
312
|
-
@config_path = config_path
|
313
|
-
super "Config file #{config_path} malformed."
|
314
|
-
end
|
315
|
-
attr_reader :config_path
|
316
|
-
end
|
317
|
-
|
318
|
-
##
|
319
|
-
# Exception raised when the given version could not be found, or no
|
320
|
-
# versions at all could be found for the given service.
|
321
|
-
#
|
322
|
-
class NoSuchVersion < UsageError
|
323
|
-
def initialize service, version = nil
|
324
|
-
@service = service
|
325
|
-
@version = version
|
326
|
-
if version
|
327
|
-
super "No such version \"#{version}\" for service \"#{service}\""
|
328
|
-
else
|
329
|
-
super "No versions found for service \"#{service}\""
|
330
|
-
end
|
331
|
-
end
|
332
|
-
attr_reader :service
|
333
|
-
attr_reader :version
|
334
|
-
end
|
335
|
-
|
336
|
-
class << self
|
337
|
-
## @return [String] Default command timeout.
|
338
|
-
attr_accessor :default_timeout
|
339
|
-
|
340
|
-
## @return [String] Default service name if the config doesn't specify.
|
341
|
-
attr_accessor :default_service
|
342
|
-
|
343
|
-
## @return [String] Path to default config file.
|
344
|
-
attr_accessor :default_config_path
|
345
|
-
|
346
|
-
## @return [String] Docker image that implements the app engine wrapper.
|
347
|
-
attr_accessor :default_wrapper_image
|
348
|
-
|
349
|
-
##
|
350
|
-
# Create an execution for a rake task.
|
351
|
-
#
|
352
|
-
# @param name [String] Name of the task
|
353
|
-
# @param args [Array<String>] Args to pass to the task
|
354
|
-
# @param env_args [Array<String>] Environment variable settings, each
|
355
|
-
# of the form `NAME=value`.
|
356
|
-
# @param service [String,nil] Name of the service. If omitted, obtains
|
357
|
-
# the service name from the config file.
|
358
|
-
# @param config_path [String,nil] App Engine config file to get the
|
359
|
-
# service name from if the service name is not provided directly.
|
360
|
-
# If omitted, defaults to the value returned by
|
361
|
-
# {AppEngine::Exec.default_config_path}.
|
362
|
-
# @param version [String,nil] Version string. If omitted, defaults to the
|
363
|
-
# most recently created version of the given service (which may not
|
364
|
-
# be the one currently receiving traffic).
|
365
|
-
# @param timeout [String,nil] Timeout string. If omitted, defaults to the
|
366
|
-
# value returned by {AppEngine::Exec.default_timeout}.
|
367
|
-
# @param wrapper_image [String,nil] The fully qualified name of the
|
368
|
-
# wrapper image to use. (Applies only to the "cloud_build" strategy.)
|
369
|
-
# @param strategy [String,nil] The execution strategy to use, or `nil` to
|
370
|
-
# choose a default based on the App Engine environment (flexible or
|
371
|
-
# standard). Allowed values are `nil`, `"deployment"` (which is the
|
372
|
-
# default for Standard), and `"cloud_build"` (which is the default
|
373
|
-
# for Flexible).
|
374
|
-
# @param gcs_log_dir [String,nil] GCS bucket name of the cloud build log
|
375
|
-
# when strategy is "cloud_build". (ex. "gs://BUCKET-NAME/FOLDER-NAME")
|
376
|
-
#
|
377
|
-
def new_rake_task name, args: [], env_args: [],
|
378
|
-
service: nil, config_path: nil, version: nil,
|
379
|
-
timeout: nil, project: nil, wrapper_image: nil,
|
380
|
-
strategy: nil, gcs_log_dir: nil
|
381
|
-
escaped_args = args.map do |arg|
|
382
|
-
arg.gsub(/[,\[\]]/) { |m| "\\#{m}" }
|
383
|
-
end
|
384
|
-
name_with_args =
|
385
|
-
if escaped_args.empty?
|
386
|
-
name
|
387
|
-
else
|
388
|
-
"#{name}[#{escaped_args.join ','}]"
|
389
|
-
end
|
390
|
-
new ["bundle", "exec", "rake", name_with_args] + env_args,
|
391
|
-
service: service, config_path: config_path, version: version,
|
392
|
-
timeout: timeout, project: project, wrapper_image: wrapper_image,
|
393
|
-
strategy: strategy, gcs_log_dir: gcs_log_dir
|
394
|
-
end
|
395
|
-
end
|
396
|
-
|
397
|
-
##
|
398
|
-
# Create an execution for the given command.
|
399
|
-
#
|
400
|
-
# @param command [Array<String>] The command in array form.
|
401
|
-
# @param project [String,nil] ID of the project. If omitted, obtains
|
402
|
-
# the project from gcloud.
|
403
|
-
# @param service [String,nil] Name of the service. If omitted, obtains
|
404
|
-
# the service name from the config file.
|
405
|
-
# @param config_path [String,nil] App Engine config file to get the
|
406
|
-
# service name from if the service name is not provided directly.
|
407
|
-
# If omitted, defaults to the value returned by
|
408
|
-
# {AppEngine::Exec.default_config_path}.
|
409
|
-
# @param version [String,nil] Version string. If omitted, defaults to the
|
410
|
-
# most recently created version of the given service (which may not be
|
411
|
-
# the one currently receiving traffic).
|
412
|
-
# @param timeout [String,nil] Timeout string. If omitted, defaults to the
|
413
|
-
# value returned by {AppEngine::Exec.default_timeout}.
|
414
|
-
# @param wrapper_image [String,nil] The fully qualified name of the wrapper
|
415
|
-
# image to use. (Applies only to the "cloud_build" strategy.)
|
416
|
-
# @param strategy [String,nil] The execution strategy to use, or `nil` to
|
417
|
-
# choose a default based on the App Engine environment (flexible or
|
418
|
-
# standard). Allowed values are `nil`, `"deployment"` (which is the
|
419
|
-
# default for Standard), and `"cloud_build"` (which is the default for
|
420
|
-
# Flexible).
|
421
|
-
# @param gcs_log_dir [String,nil] GCS bucket name of the cloud build log
|
422
|
-
# when strategy is "cloud_build". (ex. "gs://BUCKET-NAME/FOLDER-NAME")
|
423
|
-
#
|
424
|
-
def initialize command,
|
425
|
-
project: nil, service: nil, config_path: nil, version: nil,
|
426
|
-
timeout: nil, wrapper_image: nil, strategy: nil, gcs_log_dir: nil
|
427
|
-
@command = command
|
428
|
-
@service = service
|
429
|
-
@config_path = config_path
|
430
|
-
@version = version
|
431
|
-
@timeout = timeout
|
432
|
-
@project = project
|
433
|
-
@wrapper_image = wrapper_image
|
434
|
-
@strategy = strategy
|
435
|
-
@gcs_log_dir = gcs_log_dir
|
436
|
-
|
437
|
-
yield self if block_given?
|
438
|
-
end
|
439
|
-
|
440
|
-
##
|
441
|
-
# @return [String] The project ID.
|
442
|
-
# @return [nil] if the default gcloud project should be used.
|
443
|
-
#
|
444
|
-
attr_accessor :project
|
445
|
-
|
446
|
-
##
|
447
|
-
# @return [String] The service name.
|
448
|
-
# @return [nil] if the service should be obtained from the app config.
|
449
|
-
#
|
450
|
-
attr_accessor :service
|
451
|
-
|
452
|
-
##
|
453
|
-
# @return [String] Path to the config file.
|
454
|
-
# @return [nil] if the default of `./app.yaml` should be used.
|
455
|
-
#
|
456
|
-
attr_accessor :config_path
|
457
|
-
|
458
|
-
##
|
459
|
-
# @return [String] Service version of the image to use.
|
460
|
-
# @return [nil] if the most recent should be used.
|
461
|
-
#
|
462
|
-
attr_accessor :version
|
463
|
-
|
464
|
-
##
|
465
|
-
# @return [String] The command timeout, in `1h23m45s` format.
|
466
|
-
# @return [nil] if the default of `10m` should be used.
|
467
|
-
#
|
468
|
-
attr_accessor :timeout
|
469
|
-
|
470
|
-
##
|
471
|
-
# The command to run.
|
472
|
-
#
|
473
|
-
# @return [String] if the command is a script to be run in a shell.
|
474
|
-
# @return [Array<String>] if the command is a posix command to be run
|
475
|
-
# directly without a shell.
|
476
|
-
#
|
477
|
-
attr_accessor :command
|
478
|
-
|
479
|
-
##
|
480
|
-
# @return [String] Custom wrapper image to use.
|
481
|
-
# @return [nil] if the default should be used.
|
482
|
-
#
|
483
|
-
attr_accessor :wrapper_image
|
484
|
-
|
485
|
-
##
|
486
|
-
# @return [String] The execution strategy to use. Allowed values are
|
487
|
-
# `"deployment"` and `"cloud_build"`.
|
488
|
-
# @return [nil] to choose a default based on the App Engine environment
|
489
|
-
# (flexible or standard).
|
490
|
-
#
|
491
|
-
attr_accessor :strategy
|
492
|
-
|
493
|
-
##
|
494
|
-
# Executes the command synchronously. Streams the logs back to standard out
|
495
|
-
# and does not return until the command has completed or timed out.
|
496
|
-
#
|
497
|
-
def start
|
498
|
-
resolve_parameters
|
499
|
-
app_info = version_info @service, @version
|
500
|
-
resolve_strategy app_info["env"]
|
501
|
-
if @strategy == "cloud_build"
|
502
|
-
start_build_strategy app_info
|
503
|
-
else
|
504
|
-
start_deployment_strategy app_info
|
505
|
-
end
|
506
|
-
end
|
507
|
-
|
508
|
-
private
|
509
|
-
|
510
|
-
##
|
511
|
-
# @private
|
512
|
-
# Resolves and canonicalizes all the parameters.
|
513
|
-
#
|
514
|
-
def resolve_parameters
|
515
|
-
@timestamp_suffix = ::Time.now.strftime "%Y%m%d%H%M%S"
|
516
|
-
@command = ::Shellwords.split @command.to_s unless @command.is_a? Array
|
517
|
-
@project ||= default_project
|
518
|
-
@service ||= service_from_config || Exec.default_service
|
519
|
-
@version ||= latest_version @service
|
520
|
-
@timeout ||= Exec.default_timeout
|
521
|
-
@timeout_seconds = parse_timeout @timeout
|
522
|
-
@wrapper_image ||= Exec.default_wrapper_image
|
523
|
-
self
|
524
|
-
end
|
525
|
-
|
526
|
-
def resolve_strategy app_env
|
527
|
-
@strategy = @strategy.to_s.downcase
|
528
|
-
if @strategy.empty?
|
529
|
-
@strategy = app_env == "flexible" ? "cloud_build" : "deployment"
|
530
|
-
end
|
531
|
-
if app_env == "standard" && @strategy == "cloud_build" ||
|
532
|
-
@strategy != "cloud_build" && @strategy != "deployment"
|
533
|
-
raise UnsupportedStrategy.new @strategy, app_env
|
534
|
-
end
|
535
|
-
@strategy
|
536
|
-
end
|
537
|
-
|
538
|
-
def service_from_config
|
539
|
-
return nil if !@config_path && @service
|
540
|
-
@config_path ||= Exec.default_config_path
|
541
|
-
::YAML.load_file(config_path)["service"]
|
542
|
-
rescue ::Errno::ENOENT
|
543
|
-
raise ConfigFileNotFound, @config_path
|
544
|
-
rescue ::StandardError
|
545
|
-
raise BadConfigFileFormat, @config_path
|
546
|
-
end
|
547
|
-
|
548
|
-
def default_project
|
549
|
-
result = Util::Gcloud.execute \
|
550
|
-
["config", "get-value", "project"],
|
551
|
-
capture: true, assert: false
|
552
|
-
result.strip!
|
553
|
-
raise NoDefaultProject if result.empty?
|
554
|
-
result
|
555
|
-
end
|
556
|
-
|
557
|
-
def parse_timeout timeout_str
|
558
|
-
matched = timeout_str =~ /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/
|
559
|
-
raise BadParameter.new "timeout", timeout_str unless matched
|
560
|
-
hours = ::Regexp.last_match(1).to_i
|
561
|
-
minutes = ::Regexp.last_match(2).to_i
|
562
|
-
seconds = ::Regexp.last_match(3).to_i
|
563
|
-
hours * 3600 + minutes * 60 + seconds
|
564
|
-
end
|
565
|
-
|
566
|
-
##
|
567
|
-
# @private
|
568
|
-
# Returns the name of the most recently created version of the given
|
569
|
-
# service.
|
570
|
-
#
|
571
|
-
# @param service [String] Name of the service.
|
572
|
-
# @return [String] Name of the most recent version.
|
573
|
-
#
|
574
|
-
def latest_version service
|
575
|
-
result = Util::Gcloud.execute \
|
576
|
-
[
|
577
|
-
"app", "versions", "list",
|
578
|
-
"--project", @project,
|
579
|
-
"--service", service,
|
580
|
-
"--format", "get(version.id)",
|
581
|
-
"--sort-by", "~version.createTime",
|
582
|
-
"--limit", "1"
|
583
|
-
],
|
584
|
-
capture: true, assert: false
|
585
|
-
result = result.split.first
|
586
|
-
raise NoSuchVersion, service unless result
|
587
|
-
result
|
588
|
-
end
|
589
|
-
|
590
|
-
##
|
591
|
-
# @private
|
592
|
-
# Returns full information on the given version of the given service.
|
593
|
-
#
|
594
|
-
# @param service [String] Name of the service. If omitted, the service
|
595
|
-
# "default" is used.
|
596
|
-
# @param version [String] Name of the version. If omitted, the most
|
597
|
-
# recently deployed is used.
|
598
|
-
# @return [Hash] A collection of fields parsed from the JSON representation
|
599
|
-
# of the version
|
600
|
-
# @return [nil] if the requested version doesn't exist.
|
601
|
-
#
|
602
|
-
def version_info service, version
|
603
|
-
service ||= "default"
|
604
|
-
version ||= latest_version service
|
605
|
-
result = Util::Gcloud.execute \
|
606
|
-
[
|
607
|
-
"app", "versions", "describe", version,
|
608
|
-
"--project", @project,
|
609
|
-
"--service", service,
|
610
|
-
"--format", "json"
|
611
|
-
],
|
612
|
-
capture: true, assert: false
|
613
|
-
result.strip!
|
614
|
-
raise NoSuchVersion.new(service, version) if result.empty?
|
615
|
-
::JSON.parse result
|
616
|
-
end
|
617
|
-
|
618
|
-
##
|
619
|
-
# @private
|
620
|
-
# Performs exec on a GAE standard app.
|
621
|
-
#
|
622
|
-
def start_deployment_strategy app_info
|
623
|
-
describe_deployment_strategy
|
624
|
-
entrypoint_file = app_yaml_file = temp_version = nil
|
625
|
-
begin
|
626
|
-
puts "\n---------- DEPLOY COMMAND ----------"
|
627
|
-
secret = create_secret
|
628
|
-
entrypoint_file = copy_entrypoint secret
|
629
|
-
app_yaml_file = copy_app_yaml app_info, entrypoint_file
|
630
|
-
temp_version = deploy_temp_app app_yaml_file
|
631
|
-
puts "\n---------- EXECUTE COMMAND ----------"
|
632
|
-
puts "COMMAND: #{@command.inspect}\n\n"
|
633
|
-
exit_status = track_status temp_version, secret
|
634
|
-
puts "\nEXIT STATUS: #{exit_status}"
|
635
|
-
ensure
|
636
|
-
puts "\n---------- CLEANUP ----------"
|
637
|
-
::File.unlink entrypoint_file if entrypoint_file
|
638
|
-
::File.unlink app_yaml_file if app_yaml_file
|
639
|
-
delete_temp_version temp_version
|
640
|
-
end
|
641
|
-
end
|
642
|
-
|
643
|
-
def describe_deployment_strategy
|
644
|
-
puts "\nUsing the `deployment` strategy for appengine:exec"
|
645
|
-
puts "(i.e. deploying a temporary version of your app)"
|
646
|
-
puts "PROJECT: #{@project}"
|
647
|
-
puts "SERVICE: #{@service}"
|
648
|
-
puts "TIMEOUT: #{@timeout}"
|
649
|
-
end
|
650
|
-
|
651
|
-
def create_secret
|
652
|
-
::SecureRandom.alphanumeric 20
|
653
|
-
end
|
654
|
-
|
655
|
-
def copy_entrypoint secret
|
656
|
-
entrypoint_template =
|
657
|
-
::File.join(::File.dirname(::File.dirname(__dir__)),
|
658
|
-
"data", "exec_standard_entrypoint.rb.erb")
|
659
|
-
entrypoint_file = "appengine_exec_entrypoint_#{@timestamp_suffix}.rb"
|
660
|
-
erb = ::ERB.new ::File.read entrypoint_template
|
661
|
-
data = {
|
662
|
-
secret: secret.inspect, command: command.inspect
|
663
|
-
}
|
664
|
-
result = erb.result_with_hash data
|
665
|
-
::File.open entrypoint_file, "w" do |file|
|
666
|
-
file.write result
|
667
|
-
end
|
668
|
-
entrypoint_file
|
669
|
-
end
|
670
|
-
|
671
|
-
def copy_app_yaml app_info, entrypoint_file
|
672
|
-
yaml_data = {
|
673
|
-
"runtime" => app_info["runtime"],
|
674
|
-
"service" => @service,
|
675
|
-
"entrypoint" => "ruby #{entrypoint_file}",
|
676
|
-
"env_variables" => app_info["envVariables"],
|
677
|
-
"manual_scaling" => { "instances" => 1 }
|
678
|
-
}
|
679
|
-
if app_info["env"] == "flexible"
|
680
|
-
complete_flex_app_yaml yaml_data, app_info
|
681
|
-
else
|
682
|
-
complete_standard_app_yaml yaml_data, app_info
|
683
|
-
end
|
684
|
-
app_yaml_file = "appengine_exec_config_#{@timestamp_suffix}.yaml"
|
685
|
-
::File.open app_yaml_file, "w" do |file|
|
686
|
-
::Psych.dump yaml_data, file
|
687
|
-
end
|
688
|
-
app_yaml_file
|
689
|
-
end
|
690
|
-
|
691
|
-
def complete_flex_app_yaml yaml_data, app_info
|
692
|
-
yaml_data["env"] = "flex"
|
693
|
-
orig_path = (app_info["betaSettings"] || {})["module_yaml_path"]
|
694
|
-
return unless orig_path
|
695
|
-
orig_yaml = ::YAML.load_file orig_path
|
696
|
-
copy_keys = ["skip_files", "resources", "network", "runtime_config",
|
697
|
-
"beta_settings"]
|
698
|
-
copy_keys.each do |key|
|
699
|
-
yaml_data[key] = orig_yaml[key] if orig_yaml[key]
|
700
|
-
end
|
701
|
-
end
|
702
|
-
|
703
|
-
def complete_standard_app_yaml yaml_data, app_info
|
704
|
-
yaml_data["instance_class"] = app_info["instanceClass"].sub(/^F/, "B")
|
705
|
-
end
|
706
|
-
|
707
|
-
def deploy_temp_app app_yaml_file
|
708
|
-
temp_version = "appengine-exec-#{@timestamp_suffix}"
|
709
|
-
Util::Gcloud.execute [
|
710
|
-
"app", "deploy", app_yaml_file,
|
711
|
-
"--project", @project,
|
712
|
-
"--version", temp_version,
|
713
|
-
"--no-promote", "--quiet"
|
714
|
-
]
|
715
|
-
temp_version
|
716
|
-
end
|
717
|
-
|
718
|
-
def track_status temp_version, secret
|
719
|
-
host = "#{temp_version}.#{@service}.#{@project}.appspot.com"
|
720
|
-
::Net::HTTP.start host do |http|
|
721
|
-
outpos = errpos = 0
|
722
|
-
delay = 0.0
|
723
|
-
loop do
|
724
|
-
sleep delay
|
725
|
-
uri = URI("http://#{host}/#{secret}")
|
726
|
-
uri.query = ::URI.encode_www_form outpos: outpos, errpos: errpos
|
727
|
-
response = http.request_get uri
|
728
|
-
data = ::JSON.parse response.body
|
729
|
-
data["outlines"].each { |line| puts "[STDOUT] #{line}" }
|
730
|
-
data["errlines"].each { |line| puts "[STDERR] #{line}" }
|
731
|
-
outpos = data["outpos"]
|
732
|
-
errpos = data["errpos"]
|
733
|
-
return data["status"] if data["status"]
|
734
|
-
if data["time"] > @timeout_seconds
|
735
|
-
http.request_post "/#{secret}/kill", ""
|
736
|
-
return "timeout"
|
737
|
-
end
|
738
|
-
if data["outlines"].empty? && data["errlines"].empty?
|
739
|
-
delay += 0.1
|
740
|
-
delay = 1.0 if delay > 1.0
|
741
|
-
else
|
742
|
-
delay = 0.0
|
743
|
-
end
|
744
|
-
end
|
745
|
-
end
|
746
|
-
end
|
747
|
-
|
748
|
-
def delete_temp_version temp_version
|
749
|
-
Util::Gcloud.execute [
|
750
|
-
"app", "versions", "delete", temp_version,
|
751
|
-
"--project", @project,
|
752
|
-
"--service", @service,
|
753
|
-
"--quiet"
|
754
|
-
]
|
755
|
-
end
|
756
|
-
|
757
|
-
##
|
758
|
-
# @private
|
759
|
-
# Performs exec on a GAE flexible app.
|
760
|
-
#
|
761
|
-
def start_build_strategy app_info
|
762
|
-
env_variables = app_info["envVariables"] || {}
|
763
|
-
beta_settings = app_info["betaSettings"] || {}
|
764
|
-
cloud_sql_instances = beta_settings["cloud_sql_instances"] || []
|
765
|
-
container = app_info["deployment"]["container"]
|
766
|
-
image = container ? container["image"] : image_from_build(app_info)
|
767
|
-
|
768
|
-
describe_build_strategy
|
769
|
-
|
770
|
-
config = build_config command, image, env_variables, cloud_sql_instances
|
771
|
-
file = ::Tempfile.new ["cloudbuild_", ".json"]
|
772
|
-
begin
|
773
|
-
::JSON.dump config, file
|
774
|
-
file.flush
|
775
|
-
execute_command = [
|
776
|
-
"builds", "submit",
|
777
|
-
"--project", @project,
|
778
|
-
"--no-source",
|
779
|
-
"--config", file.path,
|
780
|
-
"--timeout", @timeout
|
781
|
-
]
|
782
|
-
execute_command.concat ["--gcs-log-dir", @gcs_log_dir] unless @gcs_log_dir.nil?
|
783
|
-
Util::Gcloud.execute execute_command
|
784
|
-
ensure
|
785
|
-
file.close!
|
786
|
-
end
|
787
|
-
end
|
788
|
-
|
789
|
-
##
|
790
|
-
# @private
|
791
|
-
# Workaround for https://github.com/GoogleCloudPlatform/appengine-ruby/issues/33
|
792
|
-
# Determines the image by looking it up in Cloud Build
|
793
|
-
#
|
794
|
-
def image_from_build app_info
|
795
|
-
create_time = ::DateTime.parse(app_info["createTime"]).to_time.utc
|
796
|
-
after_time = (create_time - 3600).strftime "%Y-%m-%dT%H:%M:%SZ"
|
797
|
-
before_time = (create_time + 3600).strftime "%Y-%m-%dT%H:%M:%SZ"
|
798
|
-
partial_uri = "gcr.io/#{@project}/appengine/#{@service}.#{@version}"
|
799
|
-
filter = "createTime>#{after_time} createTime<#{before_time} images[]:#{partial_uri}"
|
800
|
-
result = Util::Gcloud.execute \
|
801
|
-
[
|
802
|
-
"builds", "list",
|
803
|
-
"--project", @project,
|
804
|
-
"--filter", filter,
|
805
|
-
"--format", "json"
|
806
|
-
],
|
807
|
-
capture: true, assert: false
|
808
|
-
result.strip!
|
809
|
-
raise NoSuchVersion.new(@service, @version) if result.empty?
|
810
|
-
build_info = ::JSON.parse(result).first
|
811
|
-
build_info["images"].first
|
812
|
-
end
|
813
|
-
|
814
|
-
def describe_build_strategy
|
815
|
-
puts "\nUsing the `cloud_build` strategy for appengine:exec"
|
816
|
-
puts "(i.e. running your app image in Cloud Build)"
|
817
|
-
puts "PROJECT: #{@project}"
|
818
|
-
puts "SERVICE: #{@service}"
|
819
|
-
puts "VERSION: #{@version}"
|
820
|
-
puts "TIMEOUT: #{@timeout}"
|
821
|
-
puts ""
|
822
|
-
end
|
823
|
-
|
824
|
-
##
|
825
|
-
# @private
|
826
|
-
# Builds a cloudbuild config as a data structure.
|
827
|
-
#
|
828
|
-
# @param command [Array<String>] The command in array form.
|
829
|
-
# @param image [String] The fully qualified image path.
|
830
|
-
# @param env_variables [Hash<String,String>] Environment variables.
|
831
|
-
# @param cloud_sql_instances [String,Array<String>] Names of cloud sql
|
832
|
-
# instances to connect to.
|
833
|
-
#
|
834
|
-
def build_config command, image, env_variables, cloud_sql_instances
|
835
|
-
args = ["-i", image]
|
836
|
-
env_variables.each do |k, v|
|
837
|
-
v = v.gsub "$", "$$"
|
838
|
-
args << "-e" << "#{k}=#{v}"
|
839
|
-
end
|
840
|
-
unless cloud_sql_instances.empty?
|
841
|
-
cloud_sql_instances = Array(cloud_sql_instances)
|
842
|
-
cloud_sql_instances.each do |sql|
|
843
|
-
args << "-s" << sql
|
844
|
-
end
|
845
|
-
end
|
846
|
-
args << "--"
|
847
|
-
args += command
|
22
|
+
# The Appengine gem uses the Google Serverless gem for remote execution.
|
23
|
+
# This may be used for safe running of ops and maintenance tasks, such as
|
24
|
+
# database migrations in a production serverless environment.
|
25
|
+
# See [Google Serverless Exec](https://www.rubydoc.info/gems/google-serverless-exec)
|
26
|
+
# for more information on the usage documentation
|
848
27
|
|
849
|
-
|
850
|
-
"steps" => [
|
851
|
-
"name" => @wrapper_image,
|
852
|
-
"args" => args
|
853
|
-
]
|
854
|
-
}
|
855
|
-
end
|
856
|
-
end
|
28
|
+
Exec = Google::Serverless::Exec
|
857
29
|
end
|
data/lib/appengine/tasks.rb
CHANGED
data/lib/appengine/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appengine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Azuma
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: google-cloud-env
|
@@ -25,25 +25,39 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.4'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: google-serverless-exec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0.20'
|
34
31
|
- - ">="
|
35
32
|
- !ruby/object:Gem::Version
|
36
|
-
version: 0.
|
33
|
+
version: '0.1'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 2.a
|
37
37
|
type: :runtime
|
38
38
|
prerelease: false
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.1'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 2.a
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: stackdriver
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
40
50
|
requirements:
|
41
51
|
- - "~>"
|
42
52
|
- !ruby/object:Gem::Version
|
43
|
-
version: '0.
|
44
|
-
|
53
|
+
version: '0.21'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
45
59
|
- !ruby/object:Gem::Version
|
46
|
-
version: 0.
|
60
|
+
version: '0.21'
|
47
61
|
- !ruby/object:Gem::Dependency
|
48
62
|
name: bundler
|
49
63
|
requirement: !ruby/object:Gem::Requirement
|
@@ -64,28 +78,28 @@ dependencies:
|
|
64
78
|
requirements:
|
65
79
|
- - "~>"
|
66
80
|
- !ruby/object:Gem::Version
|
67
|
-
version: 1.
|
81
|
+
version: 1.25.1
|
68
82
|
type: :development
|
69
83
|
prerelease: false
|
70
84
|
version_requirements: !ruby/object:Gem::Requirement
|
71
85
|
requirements:
|
72
86
|
- - "~>"
|
73
87
|
- !ruby/object:Gem::Version
|
74
|
-
version: 1.
|
88
|
+
version: 1.25.1
|
75
89
|
- !ruby/object:Gem::Dependency
|
76
90
|
name: minitest
|
77
91
|
requirement: !ruby/object:Gem::Requirement
|
78
92
|
requirements:
|
79
93
|
- - "~>"
|
80
94
|
- !ruby/object:Gem::Version
|
81
|
-
version: '5.
|
95
|
+
version: '5.14'
|
82
96
|
type: :development
|
83
97
|
prerelease: false
|
84
98
|
version_requirements: !ruby/object:Gem::Requirement
|
85
99
|
requirements:
|
86
100
|
- - "~>"
|
87
101
|
- !ruby/object:Gem::Version
|
88
|
-
version: '5.
|
102
|
+
version: '5.14'
|
89
103
|
- !ruby/object:Gem::Dependency
|
90
104
|
name: minitest-focus
|
91
105
|
requirement: !ruby/object:Gem::Requirement
|
@@ -120,14 +134,14 @@ dependencies:
|
|
120
134
|
requirements:
|
121
135
|
- - "~>"
|
122
136
|
- !ruby/object:Gem::Version
|
123
|
-
version: '
|
137
|
+
version: '13.0'
|
124
138
|
type: :development
|
125
139
|
prerelease: false
|
126
140
|
version_requirements: !ruby/object:Gem::Requirement
|
127
141
|
requirements:
|
128
142
|
- - "~>"
|
129
143
|
- !ruby/object:Gem::Version
|
130
|
-
version: '
|
144
|
+
version: '13.0'
|
131
145
|
- !ruby/object:Gem::Dependency
|
132
146
|
name: rdoc
|
133
147
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,20 +170,6 @@ dependencies:
|
|
156
170
|
- - "~>"
|
157
171
|
- !ruby/object:Gem::Version
|
158
172
|
version: '3.4'
|
159
|
-
- !ruby/object:Gem::Dependency
|
160
|
-
name: toys
|
161
|
-
requirement: !ruby/object:Gem::Requirement
|
162
|
-
requirements:
|
163
|
-
- - "~>"
|
164
|
-
- !ruby/object:Gem::Version
|
165
|
-
version: '0.11'
|
166
|
-
type: :development
|
167
|
-
prerelease: false
|
168
|
-
version_requirements: !ruby/object:Gem::Requirement
|
169
|
-
requirements:
|
170
|
-
- - "~>"
|
171
|
-
- !ruby/object:Gem::Version
|
172
|
-
version: '0.11'
|
173
173
|
- !ruby/object:Gem::Dependency
|
174
174
|
name: yard
|
175
175
|
requirement: !ruby/object:Gem::Requirement
|
@@ -201,7 +201,6 @@ files:
|
|
201
201
|
- CONTRIBUTING.md
|
202
202
|
- LICENSE
|
203
203
|
- README.md
|
204
|
-
- data/exec_standard_entrypoint.rb.erb
|
205
204
|
- lib/appengine.rb
|
206
205
|
- lib/appengine/env.rb
|
207
206
|
- lib/appengine/exec.rb
|
@@ -221,14 +220,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
221
220
|
requirements:
|
222
221
|
- - ">="
|
223
222
|
- !ruby/object:Gem::Version
|
224
|
-
version: 2.
|
223
|
+
version: 2.5.0
|
225
224
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
226
225
|
requirements:
|
227
226
|
- - ">="
|
228
227
|
- !ruby/object:Gem::Version
|
229
228
|
version: '0'
|
230
229
|
requirements: []
|
231
|
-
rubygems_version: 3.
|
230
|
+
rubygems_version: 3.3.5
|
232
231
|
signing_key:
|
233
232
|
specification_version: 4
|
234
233
|
summary: Google App Engine integration tools
|
@@ -1,116 +0,0 @@
|
|
1
|
-
# Copyright 2019 Google LLC
|
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
|
-
# http://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 "webrick"
|
16
|
-
require "monitor"
|
17
|
-
require "json"
|
18
|
-
|
19
|
-
SECRET = <%= secret %>
|
20
|
-
COMMAND = Array(<%= command %>)
|
21
|
-
|
22
|
-
port = Integer ENV["PORT"]
|
23
|
-
server = ::WEBrick::HTTPServer.new Port: port
|
24
|
-
|
25
|
-
Status = ::Struct.new :out_lines, :err_lines, :exit_status, :pid, :start_time
|
26
|
-
$status = Status.new [], [], nil, nil, nil
|
27
|
-
$status.extend ::MonitorMixin
|
28
|
-
|
29
|
-
def do_start
|
30
|
-
$status.synchronize do
|
31
|
-
return unless $status.pid.nil?
|
32
|
-
end
|
33
|
-
$stdout.puts "Executing: #{COMMAND.inspect}"
|
34
|
-
$stdout.flush
|
35
|
-
rout, wout = ::IO.pipe
|
36
|
-
rerr, werr = ::IO.pipe
|
37
|
-
::Thread.new do
|
38
|
-
rout.each_line do |line|
|
39
|
-
$status.synchronize { $status.out_lines << line }
|
40
|
-
$stdout.puts line
|
41
|
-
$stdout.flush
|
42
|
-
end
|
43
|
-
end
|
44
|
-
::Thread.new do
|
45
|
-
rerr.each_line do |line|
|
46
|
-
$status.synchronize { $status.err_lines << line }
|
47
|
-
$stderr.puts line
|
48
|
-
$stderr.flush
|
49
|
-
end
|
50
|
-
end
|
51
|
-
start_time = Time.now.to_i
|
52
|
-
pid = ::Process.spawn *COMMAND, err: werr, out: wout
|
53
|
-
werr.close
|
54
|
-
wout.close
|
55
|
-
$status.synchronize do
|
56
|
-
$status.pid = pid
|
57
|
-
$status.start_time = start_time
|
58
|
-
end
|
59
|
-
::Thread.new do
|
60
|
-
_pid, status = ::Process.wait2 pid
|
61
|
-
$status.synchronize do
|
62
|
-
$status.exit_status = status.exitstatus
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def get_status outpos: 0, errpos: 0
|
68
|
-
outlines, errlines, status, start_time =
|
69
|
-
$status.synchronize do
|
70
|
-
[
|
71
|
-
$status.out_lines[outpos..-1],
|
72
|
-
$status.err_lines[errpos..-1],
|
73
|
-
$status.exit_status,
|
74
|
-
$status.start_time
|
75
|
-
]
|
76
|
-
end
|
77
|
-
{
|
78
|
-
"outpos" => outpos + outlines.size,
|
79
|
-
"errpos" => errpos + errlines.size,
|
80
|
-
"outlines" => outlines,
|
81
|
-
"errlines" => errlines,
|
82
|
-
"status" => status,
|
83
|
-
"time" => Time.now.to_i - start_time
|
84
|
-
}
|
85
|
-
end
|
86
|
-
|
87
|
-
server.mount_proc "/_ah/start" do |req, res|
|
88
|
-
do_start
|
89
|
-
end
|
90
|
-
|
91
|
-
server.mount_proc "/#{SECRET}" do |req, res|
|
92
|
-
do_start
|
93
|
-
status = get_status outpos: req.query["outpos"].to_i,
|
94
|
-
errpos: req.query["errpos"].to_i
|
95
|
-
res.body = JSON.dump status
|
96
|
-
end
|
97
|
-
|
98
|
-
server.mount_proc "/#{SECRET}/kill" do |req, res|
|
99
|
-
unless req.request_method == "POST"
|
100
|
-
res.status = 404
|
101
|
-
return
|
102
|
-
end
|
103
|
-
$status.synchronize do
|
104
|
-
if $status.pid.nil?
|
105
|
-
res.status = 404
|
106
|
-
return
|
107
|
-
end
|
108
|
-
::Process.kill "SIGTERM", $status.pid
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
begin
|
113
|
-
server.start
|
114
|
-
ensure
|
115
|
-
server.shutdown
|
116
|
-
end
|