Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mock_connect/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ FROM python:3.7-alpine
MAINTAINER RStudio Connect <rsconnect@rstudio.com>

# Add the Python packags we need.
RUN pip install flask
RUN pip install flask==2.1.3
10 changes: 5 additions & 5 deletions rsconnect/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,13 @@ def test_server(connect_server):
raise RSConnectException("\n".join(failures))


def test_shinyapps_server(server: api.ShinyappsServer):
with api.ShinyappsClient(server) as client:
def test_rstudio_server(server: api.RStudioServer):
with api.RStudioClient(server) as client:
try:
result = client.get_current_user()
server.handle_bad_response(result)
except RSConnectException as exc:
raise RSConnectException("Failed to verify with shinyapps.io ({}).".format(exc))
raise RSConnectException("Failed to verify with {} ({}).".format(server.remote_name, exc))


def test_api_key(connect_server):
Expand Down Expand Up @@ -1588,8 +1588,8 @@ def _gather_basic_deployment_info_for_framework(
if isinstance(remote_server, api.RSConnectServer):
app = api.get_app_info(remote_server, app_id)
existing_app_mode = AppModes.get_by_ordinal(app.get("app_mode", 0), True)
elif isinstance(remote_server, api.ShinyappsServer):
app = api.get_shinyapp_info(remote_server, app_id)
elif isinstance(remote_server, api.RStudioServer):
app = api.get_rstudio_app_info(remote_server, app_id)
existing_app_mode = AppModes.get_by_cloud_name(app.json_data["mode"])
else:
raise RSConnectException("Unable to infer Connect client.")
Expand Down
311 changes: 240 additions & 71 deletions rsconnect/api.py

Large diffs are not rendered by default.

83 changes: 43 additions & 40 deletions rsconnect/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
test_server,
validate_quarto_engines,
which_quarto,
test_shinyapps_server,
test_rstudio_server,
)
from .actions_content import (
download_bundle,
Expand Down Expand Up @@ -133,24 +133,24 @@ def wrapper(*args, **kwargs):
return wrapper


def shinyapps_args(func):
def rstudio_args(func):
@click.option(
"--account",
"-A",
envvar="SHINYAPPS_ACCOUNT",
help="The shinyapps.io account name.",
envvar=["SHINYAPPS_ACCOUNT", "RSCLOUD_ACCOUNT"],
help="The shinyapps.io/RStudio Cloud account name.",
)
@click.option(
"--token",
"-T",
envvar="SHINYAPPS_TOKEN",
help="The shinyapps.io token.",
envvar=["SHINYAPPS_TOKEN", "RSCLOUD_TOKEN"],
help="The shinyapps.io/RStudio Cloud token.",
)
@click.option(
"--secret",
"-S",
envvar="SHINYAPPS_SECRET",
help="The shinyapps.io token secret.",
envvar=["SHINYAPPS_SECRET", "RSCLOUD_SECRET"],
help="The shinyapps.io/RStudio Cloud token secret.",
)
@functools.wraps(func)
def wrapper(*args, **kwargs):
Expand Down Expand Up @@ -224,18 +224,21 @@ def wrapper(*args, **kwargs):
@click.option("--future", "-u", is_flag=True, hidden=True, help="Enables future functionality.")
def cli(future):
"""
This command line tool may be used to deploy Jupyter notebooks to RStudio
Connect. Support for deploying other content types is also provided.
This command line tool may be used to deploy various types of content to RStudio
Connect, RStudio Cloud, and shinyapps.io.

The tool supports the notion of a simple nickname that represents the
information needed to interact with an RStudio Connect server instance. Use
the add, list and remove commands to manage these nicknames.
information needed to interact with a deployment target. Usethe add, list and
remove commands to manage these nicknames.

The information about an instance of RStudio Connect includes its URL, the
API key needed to authenticate against that instance, a flag that notes whether
TLS certificate/host verification should be disabled and a path to a trusted CA
certificate file to use for TLS. The last two items are only relevant if the
URL specifies the "https" protocol.

For RStudio Cloud and shinyapps.io, the information needed to connect includes
the account, auth token, auth secret, and server ('rstudio.cloud' or 'shinyapps.io').
"""
global future_enabled
future_enabled = future
Expand Down Expand Up @@ -273,16 +276,16 @@ def _test_server_and_api(server, api_key, insecure, ca_cert):
return real_server, me


def _test_shinyappsio_creds(server: api.ShinyappsServer):
with cli_feedback("Checking shinyapps.io credential"):
test_shinyapps_server(server)
def _test_rstudio_creds(server: api.RStudioServer):
with cli_feedback("Checking {} credential".format(server.remote_name)):
test_rstudio_server(server)


# noinspection SpellCheckingInspection
@cli.command(
short_help="Define a nickname for an RStudio Connect or shinyapps.io server and credential.",
short_help="Define a nickname for an RStudio Connect, RStudio Cloud, or shinyapps.io server and credential.",
help=(
"Associate a simple nickname with the information needed to interact with an RStudio Connect server. "
"Associate a simple nickname with the information needed to interact with a deployment target. "
"Specifying an existing nickname will cause its stored information to be replaced by what is given "
"on the command line."
),
Expand All @@ -292,7 +295,7 @@ def _test_shinyappsio_creds(server: api.ShinyappsServer):
"--server",
"-s",
envvar="CONNECT_SERVER",
help="The URL for the RStudio Connect server to deploy to.",
help="The URL for the RStudio Connect server to deploy to, OR rstudio.cloud OR shinyapps.io.",
)
@click.option(
"--api-key",
Expand All @@ -315,7 +318,7 @@ def _test_shinyappsio_creds(server: api.ShinyappsServer):
help="The path to trusted TLS CA certificates.",
)
@click.option("--verbose", "-v", is_flag=True, help="Print detailed messages.")
@shinyapps_args
@rstudio_args
@click.pass_context
def add(ctx, name, server, api_key, insecure, cacert, account, token, secret, verbose):

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

if account:
shinyapps_server = api.ShinyappsServer(server, account, token, secret)
_test_shinyappsio_creds(shinyapps_server)
if server and "rstudio.cloud" in server:
real_server = api.CloudServer(server, account, token, secret)
else:
real_server = api.ShinyappsServer(server, account, token, secret)

_test_rstudio_creds(real_server)

server_store.set(
name,
shinyapps_server.url,
account_name=shinyapps_server.account_name,
token=shinyapps_server.token,
secret=shinyapps_server.secret,
real_server.url,
account_name=real_server.account_name,
token=real_server.token,
secret=real_server.secret,
)
if old_server:
click.echo('Updated shinyapps.io credential "%s".' % name)
click.echo('Updated {} credential "{}".'.format(real_server.remote_name, name))
else:
click.echo('Added shinyapps.io credential "%s".' % name)
click.echo('Added {} credential "{}".'.format(real_server.remote_name, name))
else:
# Server must be pingable and the API key must work to be added.
real_server, _ = _test_server_and_api(server, api_key, insecure, cacert)
Expand Down Expand Up @@ -543,7 +550,7 @@ def info(file):
click.echo("No saved deployment information was found for %s." % file)


@cli.group(no_args_is_help=True, help="Deploy content to RStudio Connect.")
@cli.group(no_args_is_help=True, help="Deploy content to RStudio Connect, RStudio Cloud, or shinyapps.io.")
def deploy():
pass

Expand Down Expand Up @@ -745,7 +752,7 @@ def deploy_notebook(
# noinspection SpellCheckingInspection,DuplicatedCode
@deploy.command(
name="manifest",
short_help="Deploy content to RStudio Connect by manifest.",
short_help="Deploy content to RStudio Connect, RStudio Cloud, or shinyapps.io by manifest.",
help=(
"Deploy content to RStudio Connect using an existing manifest.json "
'file. The specified file must either be named "manifest.json" or '
Expand All @@ -754,7 +761,7 @@ def deploy_notebook(
)
@server_args
@content_args
@shinyapps_args
@rstudio_args
@click.argument("file", type=click.Path(exists=True, dir_okay=True, file_okay=True))
@cli_exception_handler
def deploy_manifest(
Expand Down Expand Up @@ -993,23 +1000,21 @@ def deploy_html(
)


def generate_deploy_python(app_mode, alias, min_version, supported_by_shinyapps=False):
shinyapps = shinyapps_args if supported_by_shinyapps else _passthrough

def generate_deploy_python(app_mode, alias, min_version):
# noinspection SpellCheckingInspection
@deploy.command(
name=alias,
short_help="Deploy a {desc} to RStudio Connect [v{version}+].".format(
short_help="Deploy a {desc} to RStudio Connect [v{version}+], RStudio Cloud, or shinyapps.io.".format(
desc=app_mode.desc(), version=min_version
),
help=(
'Deploy a {desc} module to RStudio Connect. The "directory" argument must refer to an '
"existing directory that contains the application code."
"Deploy a {desc} module to RStudio Connect, RStudio Cloud, or shinyapps.io (if supported by the platform). "
'The "directory" argument must refer to an existing directory that contains the application code.'
).format(desc=app_mode.desc()),
)
@server_args
@content_args
@shinyapps
@rstudio_args
@click.option(
"--entrypoint",
"-e",
Expand Down Expand Up @@ -1125,9 +1130,7 @@ def deploy_app(
deploy_dash_app = generate_deploy_python(app_mode=AppModes.DASH_APP, alias="dash", min_version="1.8.2")
deploy_streamlit_app = generate_deploy_python(app_mode=AppModes.STREAMLIT_APP, alias="streamlit", min_version="1.8.4")
deploy_bokeh_app = generate_deploy_python(app_mode=AppModes.BOKEH_APP, alias="bokeh", min_version="1.8.4")
deploy_shiny = generate_deploy_python(
app_mode=AppModes.PYTHON_SHINY, alias="shiny", min_version="2022.07.0", supported_by_shinyapps=True
)
deploy_shiny = generate_deploy_python(app_mode=AppModes.PYTHON_SHINY, alias="shiny", min_version="2022.07.0")


@deploy.command(
Expand Down
6 changes: 4 additions & 2 deletions rsconnect/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ def validate_connection_options(url, api_key, insecure, cacert, account_name, to

if present_connect_options and present_shinyapps_options:
raise RSConnectException(
"Connect options ({}) may not be passed alongside shinyapps.io options ({}).".format(
"Connect options ({}) may not be passed alongside shinyapps.io or RStudio Cloud options ({}).".format(
", ".join(present_connect_options), ", ".join(present_shinyapps_options)
)
)

if present_shinyapps_options:
if len(present_shinyapps_options) != 3:
raise RSConnectException("-A/--account, -T/--token, and -S/--secret must all be provided for shinyapps.io.")
raise RSConnectException(
"-A/--account, -T/--token, and -S/--secret must all be provided for shinyapps.io or RStudio Cloud."
)
8 changes: 4 additions & 4 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

class TestAPI(TestCase):
def test_executor_init(self):
connect_server = require_connect(self)
api_key = require_api_key(self)
connect_server = require_connect()
api_key = require_api_key()
ce = RSConnectExecutor(None, connect_server, api_key, True, None)
self.assertEqual(ce.remote_server.url, connect_server)

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

def test_make_deployment_name(self):
connect_server = require_connect(self)
api_key = require_api_key(self)
connect_server = require_connect()
api_key = require_api_key()
ce = RSConnectExecutor(None, connect_server, api_key, True, None)
self.assertEqual(ce.make_deployment_name("title", False), "title")
self.assertEqual(ce.make_deployment_name("Title", False), "title")
Expand Down
Loading