Skip to content

Commit 1221c76

Browse files
author
Matthew Lynch
authored
Merge pull request #278 from rstudio/cloud-deploys
add support for deployment on RStudio Cloud
2 parents 6e3a84d + f3a9321 commit 1221c76

21 files changed

+902
-197
lines changed

mock_connect/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ FROM python:3.7-alpine
22
MAINTAINER RStudio Connect <[email protected]>
33

44
# Add the Python packags we need.
5-
RUN pip install flask
5+
RUN pip install flask==2.1.3

rsconnect/actions.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,13 @@ def test_server(connect_server):
218218
raise RSConnectException("\n".join(failures))
219219

220220

221-
def test_shinyapps_server(server: api.ShinyappsServer):
222-
with api.ShinyappsClient(server) as client:
221+
def test_rstudio_server(server: api.RStudioServer):
222+
with api.RStudioClient(server) as client:
223223
try:
224224
result = client.get_current_user()
225225
server.handle_bad_response(result)
226226
except RSConnectException as exc:
227-
raise RSConnectException("Failed to verify with shinyapps.io ({}).".format(exc))
227+
raise RSConnectException("Failed to verify with {} ({}).".format(server.remote_name, exc))
228228

229229

230230
def test_api_key(connect_server):
@@ -1588,8 +1588,8 @@ def _gather_basic_deployment_info_for_framework(
15881588
if isinstance(remote_server, api.RSConnectServer):
15891589
app = api.get_app_info(remote_server, app_id)
15901590
existing_app_mode = AppModes.get_by_ordinal(app.get("app_mode", 0), True)
1591-
elif isinstance(remote_server, api.ShinyappsServer):
1592-
app = api.get_shinyapp_info(remote_server, app_id)
1591+
elif isinstance(remote_server, api.RStudioServer):
1592+
app = api.get_rstudio_app_info(remote_server, app_id)
15931593
existing_app_mode = AppModes.get_by_cloud_name(app.json_data["mode"])
15941594
else:
15951595
raise RSConnectException("Unable to infer Connect client.")

rsconnect/api.py

Lines changed: 240 additions & 71 deletions
Large diffs are not rendered by default.

rsconnect/main.py

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
test_server,
2222
validate_quarto_engines,
2323
which_quarto,
24-
test_shinyapps_server,
24+
test_rstudio_server,
2525
)
2626
from .actions_content import (
2727
download_bundle,
@@ -133,24 +133,24 @@ def wrapper(*args, **kwargs):
133133
return wrapper
134134

135135

136-
def shinyapps_args(func):
136+
def rstudio_args(func):
137137
@click.option(
138138
"--account",
139139
"-A",
140-
envvar="SHINYAPPS_ACCOUNT",
141-
help="The shinyapps.io account name.",
140+
envvar=["SHINYAPPS_ACCOUNT", "RSCLOUD_ACCOUNT"],
141+
help="The shinyapps.io/RStudio Cloud account name.",
142142
)
143143
@click.option(
144144
"--token",
145145
"-T",
146-
envvar="SHINYAPPS_TOKEN",
147-
help="The shinyapps.io token.",
146+
envvar=["SHINYAPPS_TOKEN", "RSCLOUD_TOKEN"],
147+
help="The shinyapps.io/RStudio Cloud token.",
148148
)
149149
@click.option(
150150
"--secret",
151151
"-S",
152-
envvar="SHINYAPPS_SECRET",
153-
help="The shinyapps.io token secret.",
152+
envvar=["SHINYAPPS_SECRET", "RSCLOUD_SECRET"],
153+
help="The shinyapps.io/RStudio Cloud token secret.",
154154
)
155155
@functools.wraps(func)
156156
def wrapper(*args, **kwargs):
@@ -224,18 +224,21 @@ def wrapper(*args, **kwargs):
224224
@click.option("--future", "-u", is_flag=True, hidden=True, help="Enables future functionality.")
225225
def cli(future):
226226
"""
227-
This command line tool may be used to deploy Jupyter notebooks to RStudio
228-
Connect. Support for deploying other content types is also provided.
227+
This command line tool may be used to deploy various types of content to RStudio
228+
Connect, RStudio Cloud, and shinyapps.io.
229229
230230
The tool supports the notion of a simple nickname that represents the
231-
information needed to interact with an RStudio Connect server instance. Use
232-
the add, list and remove commands to manage these nicknames.
231+
information needed to interact with a deployment target. Usethe add, list and
232+
remove commands to manage these nicknames.
233233
234234
The information about an instance of RStudio Connect includes its URL, the
235235
API key needed to authenticate against that instance, a flag that notes whether
236236
TLS certificate/host verification should be disabled and a path to a trusted CA
237237
certificate file to use for TLS. The last two items are only relevant if the
238238
URL specifies the "https" protocol.
239+
240+
For RStudio Cloud and shinyapps.io, the information needed to connect includes
241+
the account, auth token, auth secret, and server ('rstudio.cloud' or 'shinyapps.io').
239242
"""
240243
global future_enabled
241244
future_enabled = future
@@ -273,16 +276,16 @@ def _test_server_and_api(server, api_key, insecure, ca_cert):
273276
return real_server, me
274277

275278

276-
def _test_shinyappsio_creds(server: api.ShinyappsServer):
277-
with cli_feedback("Checking shinyapps.io credential"):
278-
test_shinyapps_server(server)
279+
def _test_rstudio_creds(server: api.RStudioServer):
280+
with cli_feedback("Checking {} credential".format(server.remote_name)):
281+
test_rstudio_server(server)
279282

280283

281284
# noinspection SpellCheckingInspection
282285
@cli.command(
283-
short_help="Define a nickname for an RStudio Connect or shinyapps.io server and credential.",
286+
short_help="Define a nickname for an RStudio Connect, RStudio Cloud, or shinyapps.io server and credential.",
284287
help=(
285-
"Associate a simple nickname with the information needed to interact with an RStudio Connect server. "
288+
"Associate a simple nickname with the information needed to interact with a deployment target. "
286289
"Specifying an existing nickname will cause its stored information to be replaced by what is given "
287290
"on the command line."
288291
),
@@ -292,7 +295,7 @@ def _test_shinyappsio_creds(server: api.ShinyappsServer):
292295
"--server",
293296
"-s",
294297
envvar="CONNECT_SERVER",
295-
help="The URL for the RStudio Connect server to deploy to.",
298+
help="The URL for the RStudio Connect server to deploy to, OR rstudio.cloud OR shinyapps.io.",
296299
)
297300
@click.option(
298301
"--api-key",
@@ -315,7 +318,7 @@ def _test_shinyappsio_creds(server: api.ShinyappsServer):
315318
help="The path to trusted TLS CA certificates.",
316319
)
317320
@click.option("--verbose", "-v", is_flag=True, help="Print detailed messages.")
318-
@shinyapps_args
321+
@rstudio_args
319322
@click.pass_context
320323
def add(ctx, name, server, api_key, insecure, cacert, account, token, secret, verbose):
321324

@@ -341,20 +344,24 @@ def add(ctx, name, server, api_key, insecure, cacert, account, token, secret, ve
341344
old_server = server_store.get_by_name(name)
342345

343346
if account:
344-
shinyapps_server = api.ShinyappsServer(server, account, token, secret)
345-
_test_shinyappsio_creds(shinyapps_server)
347+
if server and "rstudio.cloud" in server:
348+
real_server = api.CloudServer(server, account, token, secret)
349+
else:
350+
real_server = api.ShinyappsServer(server, account, token, secret)
351+
352+
_test_rstudio_creds(real_server)
346353

347354
server_store.set(
348355
name,
349-
shinyapps_server.url,
350-
account_name=shinyapps_server.account_name,
351-
token=shinyapps_server.token,
352-
secret=shinyapps_server.secret,
356+
real_server.url,
357+
account_name=real_server.account_name,
358+
token=real_server.token,
359+
secret=real_server.secret,
353360
)
354361
if old_server:
355-
click.echo('Updated shinyapps.io credential "%s".' % name)
362+
click.echo('Updated {} credential "{}".'.format(real_server.remote_name, name))
356363
else:
357-
click.echo('Added shinyapps.io credential "%s".' % name)
364+
click.echo('Added {} credential "{}".'.format(real_server.remote_name, name))
358365
else:
359366
# Server must be pingable and the API key must work to be added.
360367
real_server, _ = _test_server_and_api(server, api_key, insecure, cacert)
@@ -543,7 +550,7 @@ def info(file):
543550
click.echo("No saved deployment information was found for %s." % file)
544551

545552

546-
@cli.group(no_args_is_help=True, help="Deploy content to RStudio Connect.")
553+
@cli.group(no_args_is_help=True, help="Deploy content to RStudio Connect, RStudio Cloud, or shinyapps.io.")
547554
def deploy():
548555
pass
549556

@@ -745,7 +752,7 @@ def deploy_notebook(
745752
# noinspection SpellCheckingInspection,DuplicatedCode
746753
@deploy.command(
747754
name="manifest",
748-
short_help="Deploy content to RStudio Connect by manifest.",
755+
short_help="Deploy content to RStudio Connect, RStudio Cloud, or shinyapps.io by manifest.",
749756
help=(
750757
"Deploy content to RStudio Connect using an existing manifest.json "
751758
'file. The specified file must either be named "manifest.json" or '
@@ -754,7 +761,7 @@ def deploy_notebook(
754761
)
755762
@server_args
756763
@content_args
757-
@shinyapps_args
764+
@rstudio_args
758765
@click.argument("file", type=click.Path(exists=True, dir_okay=True, file_okay=True))
759766
@cli_exception_handler
760767
def deploy_manifest(
@@ -993,23 +1000,21 @@ def deploy_html(
9931000
)
9941001

9951002

996-
def generate_deploy_python(app_mode, alias, min_version, supported_by_shinyapps=False):
997-
shinyapps = shinyapps_args if supported_by_shinyapps else _passthrough
998-
1003+
def generate_deploy_python(app_mode, alias, min_version):
9991004
# noinspection SpellCheckingInspection
10001005
@deploy.command(
10011006
name=alias,
1002-
short_help="Deploy a {desc} to RStudio Connect [v{version}+].".format(
1007+
short_help="Deploy a {desc} to RStudio Connect [v{version}+], RStudio Cloud, or shinyapps.io.".format(
10031008
desc=app_mode.desc(), version=min_version
10041009
),
10051010
help=(
1006-
'Deploy a {desc} module to RStudio Connect. The "directory" argument must refer to an '
1007-
"existing directory that contains the application code."
1011+
"Deploy a {desc} module to RStudio Connect, RStudio Cloud, or shinyapps.io (if supported by the platform). "
1012+
'The "directory" argument must refer to an existing directory that contains the application code.'
10081013
).format(desc=app_mode.desc()),
10091014
)
10101015
@server_args
10111016
@content_args
1012-
@shinyapps
1017+
@rstudio_args
10131018
@click.option(
10141019
"--entrypoint",
10151020
"-e",
@@ -1125,9 +1130,7 @@ def deploy_app(
11251130
deploy_dash_app = generate_deploy_python(app_mode=AppModes.DASH_APP, alias="dash", min_version="1.8.2")
11261131
deploy_streamlit_app = generate_deploy_python(app_mode=AppModes.STREAMLIT_APP, alias="streamlit", min_version="1.8.4")
11271132
deploy_bokeh_app = generate_deploy_python(app_mode=AppModes.BOKEH_APP, alias="bokeh", min_version="1.8.4")
1128-
deploy_shiny = generate_deploy_python(
1129-
app_mode=AppModes.PYTHON_SHINY, alias="shiny", min_version="2022.07.0", supported_by_shinyapps=True
1130-
)
1133+
deploy_shiny = generate_deploy_python(app_mode=AppModes.PYTHON_SHINY, alias="shiny", min_version="2022.07.0")
11311134

11321135

11331136
@deploy.command(

rsconnect/validation.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ def validate_connection_options(url, api_key, insecure, cacert, account_name, to
3333

3434
if present_connect_options and present_shinyapps_options:
3535
raise RSConnectException(
36-
"Connect options ({}) may not be passed alongside shinyapps.io options ({}).".format(
36+
"Connect options ({}) may not be passed alongside shinyapps.io or RStudio Cloud options ({}).".format(
3737
", ".join(present_connect_options), ", ".join(present_shinyapps_options)
3838
)
3939
)
4040

4141
if present_shinyapps_options:
4242
if len(present_shinyapps_options) != 3:
43-
raise RSConnectException("-A/--account, -T/--token, and -S/--secret must all be provided for shinyapps.io.")
43+
raise RSConnectException(
44+
"-A/--account, -T/--token, and -S/--secret must all be provided for shinyapps.io or RStudio Cloud."
45+
)

tests/test_api.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
class TestAPI(TestCase):
1010
def test_executor_init(self):
11-
connect_server = require_connect(self)
12-
api_key = require_api_key(self)
11+
connect_server = require_connect()
12+
api_key = require_api_key()
1313
ce = RSConnectExecutor(None, connect_server, api_key, True, None)
1414
self.assertEqual(ce.remote_server.url, connect_server)
1515

@@ -47,8 +47,8 @@ def test_to_server_check_list(self):
4747
self.assertEqual(a_list, ["scheme://no-scheme"])
4848

4949
def test_make_deployment_name(self):
50-
connect_server = require_connect(self)
51-
api_key = require_api_key(self)
50+
connect_server = require_connect()
51+
api_key = require_api_key()
5252
ce = RSConnectExecutor(None, connect_server, api_key, True, None)
5353
self.assertEqual(ce.make_deployment_name("title", False), "title")
5454
self.assertEqual(ce.make_deployment_name("Title", False), "title")

0 commit comments

Comments
 (0)